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/view/media_view_overlay_widget.h"
9
10 #include "apiwrap.h"
11 #include "api/api_attached_stickers.h"
12 #include "api/api_peer_photo.h"
13 #include "lang/lang_keys.h"
14 #include "mainwindow.h"
15 #include "core/application.h"
16 #include "core/click_handler_types.h"
17 #include "core/file_utilities.h"
18 #include "core/mime_type.h"
19 #include "core/ui_integration.h"
20 #include "core/crash_reports.h"
21 #include "ui/widgets/popup_menu.h"
22 #include "ui/widgets/buttons.h"
23 #include "ui/image/image.h"
24 #include "ui/text/text_utilities.h"
25 #include "ui/platform/ui_platform_utility.h"
26 #include "ui/toast/toast.h"
27 #include "ui/text/format_values.h"
28 #include "ui/item_text_options.h"
29 #include "ui/ui_utility.h"
30 #include "ui/cached_round_corners.h"
31 #include "ui/gl/gl_surface.h"
32 #include "ui/boxes/confirm_box.h"
33 #include "boxes/delete_messages_box.h"
34 #include "media/audio/media_audio.h"
35 #include "media/view/media_view_playback_controls.h"
36 #include "media/view/media_view_group_thumbs.h"
37 #include "media/view/media_view_pip.h"
38 #include "media/view/media_view_overlay_raster.h"
39 #include "media/view/media_view_overlay_opengl.h"
40 #include "media/streaming/media_streaming_instance.h"
41 #include "media/streaming/media_streaming_player.h"
42 #include "media/player/media_player_instance.h"
43 #include "history/history.h"
44 #include "history/history_message.h"
45 #include "history/view/media/history_view_media.h"
46 #include "data/data_media_types.h"
47 #include "data/data_session.h"
48 #include "data/data_channel.h"
49 #include "data/data_chat.h"
50 #include "data/data_user.h"
51 #include "data/data_file_origin.h"
52 #include "data/data_media_rotation.h"
53 #include "data/data_photo_media.h"
54 #include "data/data_document_media.h"
55 #include "data/data_document_resolver.h"
56 #include "data/data_file_click_handler.h"
57 #include "window/themes/window_theme_preview.h"
58 #include "window/window_peer_menu.h"
59 #include "window/window_session_controller.h"
60 #include "window/window_controller.h"
61 #include "base/platform/base_platform_info.h"
62 #include "base/random.h"
63 #include "base/unixtime.h"
64 #include "base/qt_signal_producer.h"
65 #include "base/event_filter.h"
66 #include "main/main_account.h"
67 #include "main/main_domain.h" // Domain::activeSessionValue.
68 #include "main/main_session.h"
69 #include "main/main_session_settings.h"
70 #include "layout/layout_document_generic_preview.h"
71 #include "storage/file_download.h"
72 #include "storage/storage_account.h"
73 #include "calls/calls_instance.h"
74 #include "facades.h"
75 #include "styles/style_media_view.h"
76 #include "styles/style_chat.h"
77 #include "base/qt_adapters.h"
78
79 #ifdef Q_OS_MAC
80 #include "platform/mac/touchbar/mac_touchbar_media_view.h"
81 #endif // Q_OS_MAC
82
83 #include <QtWidgets/QApplication>
84 #include <QtCore/QBuffer>
85 #include <QtGui/QGuiApplication>
86 #include <QtGui/QClipboard>
87 #include <QtGui/QWindow>
88 #include <QtGui/QScreen>
89
90 namespace Media {
91 namespace View {
92 namespace {
93
94 constexpr auto kPreloadCount = 3;
95 constexpr auto kMaxZoomLevel = 7; // x8
96 constexpr auto kZoomToScreenLevel = 1024;
97 constexpr auto kOverlayLoaderPriority = 2;
98 constexpr auto kSeekTimeMs = 5 * crl::time(1000);
99
100 // macOS OpenGL renderer fails to render larger texture
101 // even though it reports that max texture size is 16384.
102 constexpr auto kMaxDisplayImageSize = 4096;
103
104 // Preload X message ids before and after current.
105 constexpr auto kIdsLimit = 48;
106
107 // Preload next messages if we went further from current than that.
108 constexpr auto kIdsPreloadAfter = 28;
109
110 class PipDelegate final : public Pip::Delegate {
111 public:
112 PipDelegate(QWidget *parent, not_null<Main::Session*> session);
113
114 void pipSaveGeometry(QByteArray geometry) override;
115 QByteArray pipLoadGeometry() override;
116 float64 pipPlaybackSpeed() override;
117 QWidget *pipParentWidget() override;
118
119 private:
120 QWidget *_parent = nullptr;
121 not_null<Main::Session*> _session;
122
123 };
124
PipDelegate(QWidget * parent,not_null<Main::Session * > session)125 PipDelegate::PipDelegate(QWidget *parent, not_null<Main::Session*> session)
126 : _parent(parent)
127 , _session(session) {
128 }
129
pipSaveGeometry(QByteArray geometry)130 void PipDelegate::pipSaveGeometry(QByteArray geometry) {
131 Core::App().settings().setVideoPipGeometry(geometry);
132 Core::App().saveSettingsDelayed();
133 }
134
pipLoadGeometry()135 QByteArray PipDelegate::pipLoadGeometry() {
136 return Core::App().settings().videoPipGeometry();
137 }
138
pipPlaybackSpeed()139 float64 PipDelegate::pipPlaybackSpeed() {
140 return Core::App().settings().videoPlaybackSpeed();
141 }
142
pipParentWidget()143 QWidget *PipDelegate::pipParentWidget() {
144 return _parent;
145 }
146
VideoThumbOptions(DocumentData * document)147 [[nodiscard]] Images::Options VideoThumbOptions(DocumentData *document) {
148 const auto result = Images::Option::Smooth | Images::Option::Blurred;
149 return (document && document->isVideoMessage())
150 ? (result | Images::Option::Circled)
151 : result;
152 }
153
PrepareStaticImage(Images::ReadArgs && args)154 [[nodiscard]] QImage PrepareStaticImage(Images::ReadArgs &&args) {
155 auto read = Images::Read(std::move(args));
156 return (read.image.width() > kMaxDisplayImageSize
157 || read.image.height() > kMaxDisplayImageSize)
158 ? read.image.scaled(
159 kMaxDisplayImageSize,
160 kMaxDisplayImageSize,
161 Qt::KeepAspectRatio,
162 Qt::SmoothTransformation)
163 : read.image;
164 }
165
IsSemitransparent(const QImage & image)166 [[nodiscard]] bool IsSemitransparent(const QImage &image) {
167 if (image.isNull()) {
168 return true;
169 } else if (!image.hasAlphaChannel()) {
170 return false;
171 }
172 Assert(image.format() == QImage::Format_ARGB32_Premultiplied);
173 constexpr auto kAlphaMask = 0xFF000000;
174 auto ints = reinterpret_cast<const uint32*>(image.bits());
175 const auto add = (image.bytesPerLine() / 4) - image.width();
176 for (auto y = 0; y != image.height(); ++y) {
177 for (auto till = ints + image.width(); ints != till; ++ints) {
178 if ((*ints & kAlphaMask) != kAlphaMask) {
179 return true;
180 }
181 }
182 ints += add;
183 }
184 return false;
185 }
186
187 } // namespace
188
189 struct OverlayWidget::SharedMedia {
SharedMediaMedia::View::OverlayWidget::SharedMedia190 SharedMedia(SharedMediaKey key) : key(key) {
191 }
192
193 SharedMediaKey key;
194 rpl::lifetime lifetime;
195 };
196
197 struct OverlayWidget::UserPhotos {
UserPhotosMedia::View::OverlayWidget::UserPhotos198 UserPhotos(UserPhotosKey key) : key(key) {
199 }
200
201 UserPhotosKey key;
202 rpl::lifetime lifetime;
203 };
204
205 struct OverlayWidget::Collage {
CollageMedia::View::OverlayWidget::Collage206 Collage(CollageKey key) : key(key) {
207 }
208
209 CollageKey key;
210 };
211
212 struct OverlayWidget::Streamed {
213 Streamed(
214 not_null<DocumentData*> document,
215 Data::FileOrigin origin,
216 not_null<QWidget*> controlsParent,
217 not_null<PlaybackControls::Delegate*> controlsDelegate,
218 Fn<void()> waitingCallback);
219 Streamed(
220 not_null<PhotoData*> photo,
221 Data::FileOrigin origin,
222 not_null<QWidget*> controlsParent,
223 not_null<PlaybackControls::Delegate*> controlsDelegate,
224 Fn<void()> waitingCallback);
225
226 Streaming::Instance instance;
227 PlaybackControls controls;
228
229 bool withSound = false;
230 bool pausedBySeek = false;
231 bool resumeOnCallEnd = false;
232 };
233
234 struct OverlayWidget::PipWrap {
235 PipWrap(
236 QWidget *parent,
237 not_null<DocumentData*> document,
238 FullMsgId contextId,
239 std::shared_ptr<Streaming::Document> shared,
240 FnMut<void()> closeAndContinue,
241 FnMut<void()> destroy);
242
243 PipWrap(const PipWrap &other) = delete;
244 PipWrap &operator=(const PipWrap &other) = delete;
245
246 PipDelegate delegate;
247 Pip wrapped;
248 };
249
Streamed(not_null<DocumentData * > document,Data::FileOrigin origin,not_null<QWidget * > controlsParent,not_null<PlaybackControls::Delegate * > controlsDelegate,Fn<void ()> waitingCallback)250 OverlayWidget::Streamed::Streamed(
251 not_null<DocumentData*> document,
252 Data::FileOrigin origin,
253 not_null<QWidget*> controlsParent,
254 not_null<PlaybackControls::Delegate*> controlsDelegate,
255 Fn<void()> waitingCallback)
256 : instance(document, origin, std::move(waitingCallback))
257 , controls(controlsParent, controlsDelegate) {
258 }
259
Streamed(not_null<PhotoData * > photo,Data::FileOrigin origin,not_null<QWidget * > controlsParent,not_null<PlaybackControls::Delegate * > controlsDelegate,Fn<void ()> waitingCallback)260 OverlayWidget::Streamed::Streamed(
261 not_null<PhotoData*> photo,
262 Data::FileOrigin origin,
263 not_null<QWidget*> controlsParent,
264 not_null<PlaybackControls::Delegate*> controlsDelegate,
265 Fn<void()> waitingCallback)
266 : instance(photo, origin, std::move(waitingCallback))
267 , controls(controlsParent, controlsDelegate) {
268 }
269
PipWrap(QWidget * parent,not_null<DocumentData * > document,FullMsgId contextId,std::shared_ptr<Streaming::Document> shared,FnMut<void ()> closeAndContinue,FnMut<void ()> destroy)270 OverlayWidget::PipWrap::PipWrap(
271 QWidget *parent,
272 not_null<DocumentData*> document,
273 FullMsgId contextId,
274 std::shared_ptr<Streaming::Document> shared,
275 FnMut<void()> closeAndContinue,
276 FnMut<void()> destroy)
277 : delegate(parent, &document->session())
278 , wrapped(
279 &delegate,
280 document,
281 contextId,
282 std::move(shared),
283 std::move(closeAndContinue),
284 std::move(destroy)) {
285 }
286
OverlayWidget()287 OverlayWidget::OverlayWidget()
288 : _surface(Ui::GL::CreateSurface(
289 [=](Ui::GL::Capabilities capabilities) {
290 return chooseRenderer(capabilities);
291 }))
292 , _widget(_surface->rpWidget())
293 , _docDownload(_widget, tr::lng_media_download(tr::now), st::mediaviewFileLink)
294 , _docSaveAs(_widget, tr::lng_mediaview_save_as(tr::now), st::mediaviewFileLink)
295 , _docCancel(_widget, tr::lng_cancel(tr::now), st::mediaviewFileLink)
__anon022521c50302(crl::time now) 296 , _radial([=](crl::time now) { return radialAnimationCallback(now); })
297 , _lastAction(-st::mediaviewDeltaFromLastAction, -st::mediaviewDeltaFromLastAction)
__anon022521c50402(crl::time now) 298 , _stateAnimation([=](crl::time now) { return stateAnimationCallback(now); })
299 , _dropdown(_widget, st::mediaviewDropdownMenu) {
300 CrashReports::SetAnnotation("OpenGL Renderer", "[not-initialized]");
301
302 Lang::Updated(
__anon022521c50502null303 ) | rpl::start_with_next([=] {
304 refreshLang();
305 }, lifetime());
306
307 _lastPositiveVolume = (Core::App().settings().videoVolume() > 0.)
308 ? Core::App().settings().videoVolume()
309 : Core::Settings::kDefaultVolume;
310
311 _widget->setWindowTitle(qsl("Media viewer"));
312
313 const auto text = tr::lng_mediaview_saved_to(
314 tr::now,
315 lt_downloads,
316 Ui::Text::Link(
317 tr::lng_mediaview_downloads(tr::now),
318 "internal:show_saved_message"),
319 Ui::Text::WithEntities);
320 _saveMsgText.setMarkedText(st::mediaviewSaveMsgStyle, text, Ui::DialogTextOptions());
321 _saveMsg = QRect(0, 0, _saveMsgText.maxWidth() + st::mediaviewSaveMsgPadding.left() + st::mediaviewSaveMsgPadding.right(), st::mediaviewSaveMsgStyle.font->height + st::mediaviewSaveMsgPadding.top() + st::mediaviewSaveMsgPadding.bottom());
322 _saveMsgImage = QImage(
323 _saveMsg.size() * cIntRetinaFactor(),
324 QImage::Format_ARGB32_Premultiplied);
325
326 _docRectImage = QImage(
327 st::mediaviewFileSize * cIntRetinaFactor(),
328 QImage::Format_ARGB32_Premultiplied);
329 _docRectImage.setDevicePixelRatio(cIntRetinaFactor());
330
331 _surface->shownValue(
__anon022521c50602(bool shown) 332 ) | rpl::start_with_next([=](bool shown) {
333 toggleApplicationEventFilter(shown);
334 if (shown) {
335 const auto screenList = QGuiApplication::screens();
336 DEBUG_LOG(("Viewer Pos: Shown, screen number: %1")
337 .arg(screenList.indexOf(window()->screen())));
338 moveToScreen();
339 } else {
340 clearAfterHide();
341 }
342 }, lifetime());
343
__anon022521c50702(not_null<QEvent*> e) 344 const auto mousePosition = [](not_null<QEvent*> e) {
345 return static_cast<QMouseEvent*>(e.get())->pos();
346 };
__anon022521c50802(not_null<QEvent*> e) 347 const auto mouseButton = [](not_null<QEvent*> e) {
348 return static_cast<QMouseEvent*>(e.get())->button();
349 };
__anon022521c50902(not_null<QEvent*> e) 350 base::install_event_filter(_widget, [=](not_null<QEvent*> e) {
351 const auto type = e->type();
352 if (type == QEvent::Move) {
353 const auto position = static_cast<QMoveEvent*>(e.get())->pos();
354 DEBUG_LOG(("Viewer Pos: Moved to %1, %2")
355 .arg(position.x())
356 .arg(position.y()));
357 } else if (type == QEvent::Resize) {
358 const auto size = static_cast<QResizeEvent*>(e.get())->size();
359 DEBUG_LOG(("Viewer Pos: Resized to %1, %2")
360 .arg(size.width())
361 .arg(size.height()));
362 updateControlsGeometry();
363 } else if (type == QEvent::MouseButtonPress) {
364 handleMousePress(mousePosition(e), mouseButton(e));
365 } else if (type == QEvent::MouseButtonRelease) {
366 handleMouseRelease(mousePosition(e), mouseButton(e));
367 } else if (type == QEvent::MouseMove) {
368 handleMouseMove(mousePosition(e));
369 } else if (type == QEvent::KeyPress) {
370 handleKeyPress(static_cast<QKeyEvent*>(e.get()));
371 } else if (type == QEvent::ContextMenu) {
372 const auto event = static_cast<QContextMenuEvent*>(e.get());
373 const auto mouse = (event->reason() == QContextMenuEvent::Mouse);
374 const auto position = mouse
375 ? std::make_optional(event->pos())
376 : std::nullopt;
377 if (handleContextMenu(position)) {
378 return base::EventFilterResult::Cancel;
379 }
380 } else if (type == QEvent::MouseButtonDblClick) {
381 if (handleDoubleClick(mousePosition(e), mouseButton(e))) {
382 return base::EventFilterResult::Cancel;
383 } else {
384 handleMousePress(mousePosition(e), mouseButton(e));
385 }
386 } else if (type == QEvent::TouchBegin
387 || type == QEvent::TouchUpdate
388 || type == QEvent::TouchEnd
389 || type == QEvent::TouchCancel) {
390 if (handleTouchEvent(static_cast<QTouchEvent*>(e.get()))) {
391 return base::EventFilterResult::Cancel;;
392 }
393 } else if (type == QEvent::Wheel) {
394 handleWheelEvent(static_cast<QWheelEvent*>(e.get()));
395 }
396 return base::EventFilterResult::Continue;
397 });
398
399 if (Platform::IsLinux()) {
400 _widget->setWindowFlags(Qt::FramelessWindowHint
401 | Qt::MaximizeUsingFullscreenGeometryHint);
402 } else if (Platform::IsMac()) {
403 // Without Qt::Tool starting with Qt 5.15.1 this widget
404 // when being opened from a fullscreen main window was
405 // opening not as overlay over the main window, but as
406 // a separate fullscreen window with a separate space.
407 _widget->setWindowFlags(Qt::FramelessWindowHint | Qt::Tool);
408 } else {
409 _widget->setWindowFlags(Qt::FramelessWindowHint);
410 }
411 _widget->setAttribute(Qt::WA_NoSystemBackground, true);
412 _widget->setAttribute(Qt::WA_TranslucentBackground, true);
413 _widget->setMouseTracking(true);
414
415 hide();
416 _widget->createWinId();
417 if (Platform::IsLinux()) {
418 window()->setTransientParent(App::wnd()->windowHandle());
419 _widget->setWindowModality(Qt::WindowModal);
420 }
421 if (!Platform::IsMac()) {
422 _widget->setWindowState(Qt::WindowFullScreen);
423 }
424
425 QObject::connect(
426 window(),
427 &QWindow::screenChanged,
__anon022521c50a02(QScreen *screen) 428 [=](QScreen *screen) { handleScreenChanged(screen); });
429 subscribeToScreenGeometry();
430 updateGeometry();
431 updateControlsGeometry();
432
433 #ifdef Q_OS_MAC
434 TouchBar::SetupMediaViewTouchBar(
435 _widget->winId(),
436 static_cast<PlaybackControls::Delegate*>(this),
437 _touchbarTrackState.events(),
438 _touchbarDisplay.events(),
439 _touchbarFullscreenToggled.events());
440 #endif // Q_OS_MAC
441
442 using namespace rpl::mappers;
443 rpl::combine(
444 Core::App().calls().currentCallValue(),
445 Core::App().calls().currentGroupCallValue(),
446 _1 || _2
__anon022521c50b02(bool call) 447 ) | rpl::start_with_next([=](bool call) {
448 if (!_streamed || videoIsGifOrUserpic()) {
449 return;
450 } else if (call) {
451 playbackPauseOnCall();
452 } else {
453 playbackResumeOnCall();
454 }
455 }, lifetime());
456
__anon022521c50c02null457 _saveMsgUpdater.setCallback([=] { updateImage(); });
458
459 _widget->setAttribute(Qt::WA_AcceptTouchEvents);
__anon022521c50d02null460 _touchTimer.setCallback([=] { handleTouchTimer(); });
461
__anon022521c50e02null462 _controlsHideTimer.setCallback([=] { hideControls(); });
463
__anon022521c50f02null464 _docDownload->addClickHandler([=] { downloadMedia(); });
__anon022521c51002null465 _docSaveAs->addClickHandler([=] { saveAs(); });
__anon022521c51102null466 _docCancel->addClickHandler([=] { saveCancel(); });
467
__anon022521c51202null468 _dropdown->setHiddenCallback([this] { dropdownHidden(); });
__anon022521c51302null469 _dropdownShowTimer.setCallback([=] { showDropdown(); });
470 }
471
refreshLang()472 void OverlayWidget::refreshLang() {
473 InvokeQueued(_widget, [=] { updateThemePreviewGeometry(); });
474 }
475
moveToScreen()476 void OverlayWidget::moveToScreen() {
477 const auto widgetScreen = [&](auto &&widget) -> QScreen* {
478 if (auto handle = widget ? widget->windowHandle() : nullptr) {
479 return handle->screen();
480 }
481 return nullptr;
482 };
483 const auto applicationWindow = Core::App().activeWindow()
484 ? Core::App().activeWindow()->widget().get()
485 : nullptr;
486 const auto activeWindowScreen = widgetScreen(applicationWindow);
487 const auto myScreen = widgetScreen(_widget);
488 if (activeWindowScreen && myScreen != activeWindowScreen) {
489 const auto screenList = QGuiApplication::screens();
490 DEBUG_LOG(("Viewer Pos: Currently on screen %1, moving to screen %2")
491 .arg(screenList.indexOf(myScreen))
492 .arg(screenList.indexOf(activeWindowScreen)));
493 window()->setScreen(activeWindowScreen);
494 DEBUG_LOG(("Viewer Pos: New actual screen: %1")
495 .arg(screenList.indexOf(window()->screen())));
496 }
497 updateGeometry();
498 }
499
updateGeometry()500 void OverlayWidget::updateGeometry() {
501 if (Platform::IsWayland()) {
502 return;
503 }
504 const auto screen = window()->screen()
505 ? window()->screen()
506 : QApplication::primaryScreen();
507 const auto available = screen->geometry();
508 const auto openglWidget = _opengl
509 ? static_cast<QOpenGLWidget*>(_widget.get())
510 : nullptr;
511 const auto useSizeHack = Platform::IsWindows()
512 && openglWidget
513 && (openglWidget->format().renderableType()
514 != QSurfaceFormat::OpenGLES);
515 const auto use = useSizeHack
516 ? available.marginsAdded({ 0, 0, 0, 1 })
517 : available;
518 const auto mask = useSizeHack
519 ? QRegion(QRect(QPoint(), available.size()))
520 : QRegion();
521 if ((_widget->geometry() == use)
522 && (!useSizeHack || window()->mask() == mask)) {
523 return;
524 }
525 DEBUG_LOG(("Viewer Pos: Setting %1, %2, %3, %4")
526 .arg(use.x())
527 .arg(use.y())
528 .arg(use.width())
529 .arg(use.height()));
530 _widget->setGeometry(use);
531 if (useSizeHack) {
532 window()->setMask(mask);
533 }
534 }
535
updateControlsGeometry()536 void OverlayWidget::updateControlsGeometry() {
537 auto navSkip = 2 * st::mediaviewControlMargin + st::mediaviewControlSize;
538 _closeNav = QRect(width() - st::mediaviewControlMargin - st::mediaviewControlSize, st::mediaviewControlMargin, st::mediaviewControlSize, st::mediaviewControlSize);
539 _closeNavIcon = style::centerrect(_closeNav, st::mediaviewClose);
540 _leftNav = QRect(st::mediaviewControlMargin, navSkip, st::mediaviewControlSize, height() - 2 * navSkip);
541 _leftNavIcon = style::centerrect(_leftNav, st::mediaviewLeft);
542 _rightNav = QRect(width() - st::mediaviewControlMargin - st::mediaviewControlSize, navSkip, st::mediaviewControlSize, height() - 2 * navSkip);
543 _rightNavIcon = style::centerrect(_rightNav, st::mediaviewRight);
544
545 _saveMsg.moveTo((width() - _saveMsg.width()) / 2, (height() - _saveMsg.height()) / 2);
546 _photoRadialRect = QRect(QPoint((width() - st::radialSize.width()) / 2, (height() - st::radialSize.height()) / 2), st::radialSize);
547
548 updateControls();
549 resizeContentByScreenSize();
550 update();
551 }
552
flipSizeByRotation(QSize size) const553 QSize OverlayWidget::flipSizeByRotation(QSize size) const {
554 return FlipSizeByRotation(size, _rotation);
555 }
556
videoShown() const557 bool OverlayWidget::videoShown() const {
558 return _streamed && !_streamed->instance.info().video.cover.isNull();
559 }
560
videoSize() const561 QSize OverlayWidget::videoSize() const {
562 Expects(videoShown());
563
564 return flipSizeByRotation(_streamed->instance.info().video.size);
565 }
566
videoIsGifOrUserpic() const567 bool OverlayWidget::videoIsGifOrUserpic() const {
568 return _streamed
569 && (!_document
570 || (_document->isAnimation() && !_document->isVideoMessage()));
571 }
572
videoFrame() const573 QImage OverlayWidget::videoFrame() const {
574 Expects(videoShown());
575
576 auto request = Streaming::FrameRequest();
577 //request.radius = (_document && _document->isVideoMessage())
578 // ? ImageRoundRadius::Ellipse
579 // : ImageRoundRadius::None;
580 return _streamed->instance.player().ready()
581 ? _streamed->instance.frame(request)
582 : _streamed->instance.info().video.cover;
583 }
584
videoFrameWithInfo() const585 Streaming::FrameWithInfo OverlayWidget::videoFrameWithInfo() const {
586 Expects(videoShown());
587
588 return _streamed->instance.player().ready()
589 ? _streamed->instance.frameWithInfo()
590 : Streaming::FrameWithInfo{
591 .original = _streamed->instance.info().video.cover,
592 .format = Streaming::FrameFormat::ARGB32,
593 .index = -2,
594 };
595 }
596
currentVideoFrameImage() const597 QImage OverlayWidget::currentVideoFrameImage() const {
598 return _streamed->instance.player().ready()
599 ? _streamed->instance.player().currentFrameImage()
600 : _streamed->instance.info().video.cover;
601 }
602
streamedIndex() const603 int OverlayWidget::streamedIndex() const {
604 return _streamedCreated;
605 }
606
documentContentShown() const607 bool OverlayWidget::documentContentShown() const {
608 return _document && (!_staticContent.isNull() || videoShown());
609 }
610
documentBubbleShown() const611 bool OverlayWidget::documentBubbleShown() const {
612 return (!_photo && !_document)
613 || (_document
614 && !_themePreviewShown
615 && !_streamed
616 && _staticContent.isNull());
617 }
618
setStaticContent(QImage image)619 void OverlayWidget::setStaticContent(QImage image) {
620 constexpr auto kGood = QImage::Format_ARGB32_Premultiplied;
621 if (!image.isNull()
622 && image.format() != kGood
623 && image.format() != QImage::Format_RGB32) {
624 image = std::move(image).convertToFormat(kGood);
625 }
626 image.setDevicePixelRatio(cRetinaFactor());
627 _staticContent = std::move(image);
628 _staticContentTransparent = IsSemitransparent(_staticContent);
629 }
630
contentShown() const631 bool OverlayWidget::contentShown() const {
632 return _photo || documentContentShown();
633 }
634
opaqueContentShown() const635 bool OverlayWidget::opaqueContentShown() const {
636 return contentShown()
637 && (!_staticContentTransparent
638 || !_document
639 || (!_document->isVideoMessage() && !_document->sticker()));
640 }
641
clearStreaming(bool savePosition)642 void OverlayWidget::clearStreaming(bool savePosition) {
643 if (_streamed && _document && savePosition) {
644 Media::Player::SaveLastPlaybackPosition(
645 _document,
646 _streamed->instance.player().prepareLegacyState());
647 }
648 _fullScreenVideo = false;
649 _streamed = nullptr;
650 }
651
documentUpdated(DocumentData * doc)652 void OverlayWidget::documentUpdated(DocumentData *doc) {
653 if (_document && _document == doc) {
654 if (documentBubbleShown()) {
655 if ((_document->loading() && _docCancel->isHidden()) || (!_document->loading() && !_docCancel->isHidden())) {
656 updateControls();
657 } else if (_document->loading()) {
658 updateDocSize();
659 _widget->update(_docRect);
660 }
661 } else if (_streamed) {
662 const auto ready = _documentMedia->loaded()
663 ? _document->size
664 : _document->loading()
665 ? std::clamp(_document->loadOffset(), 0, _document->size)
666 : 0;
667 _streamed->controls.setLoadingProgress(ready, _document->size);
668 }
669 }
670 }
671
changingMsgId(not_null<HistoryItem * > row,MsgId oldId)672 void OverlayWidget::changingMsgId(not_null<HistoryItem*> row, MsgId oldId) {
673 if (row == _message) {
674 refreshMediaViewer();
675 }
676 }
677
updateDocSize()678 void OverlayWidget::updateDocSize() {
679 if (!_document || !documentBubbleShown()) {
680 return;
681 }
682
683 const auto size = _document->size;
684 _docSize = _document->loading()
685 ? Ui::FormatProgressText(_document->loadOffset(), size)
686 : Ui::FormatSizeText(size);
687 _docSizeWidth = st::mediaviewFont->width(_docSize);
688 int32 maxw = st::mediaviewFileSize.width() - st::mediaviewFileIconSize - st::mediaviewFilePadding * 3;
689 if (_docSizeWidth > maxw) {
690 _docSize = st::mediaviewFont->elided(_docSize, maxw);
691 _docSizeWidth = st::mediaviewFont->width(_docSize);
692 }
693 }
694
refreshNavVisibility()695 void OverlayWidget::refreshNavVisibility() {
696 if (_sharedMediaData) {
697 _leftNavVisible = _index && (*_index > 0);
698 _rightNavVisible = _index && (*_index + 1 < _sharedMediaData->size());
699 } else if (_userPhotosData) {
700 _leftNavVisible = _index && (*_index > 0);
701 _rightNavVisible = _index && (*_index + 1 < _userPhotosData->size());
702 } else if (_collageData) {
703 _leftNavVisible = _index && (*_index > 0);
704 _rightNavVisible = _index && (*_index + 1 < _collageData->items.size());
705 } else {
706 _leftNavVisible = false;
707 _rightNavVisible = false;
708 }
709 }
710
contentCanBeSaved() const711 bool OverlayWidget::contentCanBeSaved() const {
712 if (_photo) {
713 return _photo->hasVideo() || _photoMedia->loaded();
714 } else if (_document) {
715 return _document->filepath(true).isEmpty() && !_document->loading();
716 } else {
717 return false;
718 }
719 }
720
checkForSaveLoaded()721 void OverlayWidget::checkForSaveLoaded() {
722 if (_savePhotoVideoWhenLoaded == SavePhotoVideo::None) {
723 return;
724 } else if (!_photo
725 || !_photo->hasVideo()
726 || _photoMedia->videoContent().isEmpty()) {
727 return;
728 } else if (_savePhotoVideoWhenLoaded == SavePhotoVideo::QuickSave) {
729 _savePhotoVideoWhenLoaded = SavePhotoVideo::None;
730 downloadMedia();
731 } else if (_savePhotoVideoWhenLoaded == SavePhotoVideo::SaveAs) {
732 _savePhotoVideoWhenLoaded = SavePhotoVideo::None;
733 saveAs();
734 } else {
735 Unexpected("SavePhotoVideo in OverlayWidget::checkForSaveLoaded.");
736 }
737 }
738
updateControls()739 void OverlayWidget::updateControls() {
740 if (_document && documentBubbleShown()) {
741 _docRect = QRect(
742 (width() - st::mediaviewFileSize.width()) / 2,
743 (height() - st::mediaviewFileSize.height()) / 2,
744 st::mediaviewFileSize.width(),
745 st::mediaviewFileSize.height());
746 _docIconRect = QRect(
747 _docRect.x() + st::mediaviewFilePadding,
748 _docRect.y() + st::mediaviewFilePadding,
749 st::mediaviewFileIconSize,
750 st::mediaviewFileIconSize);
751 if (_document->loading()) {
752 _docDownload->hide();
753 _docSaveAs->hide();
754 _docCancel->moveToLeft(_docRect.x() + 2 * st::mediaviewFilePadding + st::mediaviewFileIconSize, _docRect.y() + st::mediaviewFilePadding + st::mediaviewFileLinksTop);
755 _docCancel->show();
756 } else {
757 if (_documentMedia->loaded(true)) {
758 _docDownload->hide();
759 _docSaveAs->moveToLeft(_docRect.x() + 2 * st::mediaviewFilePadding + st::mediaviewFileIconSize, _docRect.y() + st::mediaviewFilePadding + st::mediaviewFileLinksTop);
760 _docSaveAs->show();
761 _docCancel->hide();
762 } else {
763 _docDownload->moveToLeft(_docRect.x() + 2 * st::mediaviewFilePadding + st::mediaviewFileIconSize, _docRect.y() + st::mediaviewFilePadding + st::mediaviewFileLinksTop);
764 _docDownload->show();
765 _docSaveAs->moveToLeft(_docRect.x() + 2.5 * st::mediaviewFilePadding + st::mediaviewFileIconSize + _docDownload->width(), _docRect.y() + st::mediaviewFilePadding + st::mediaviewFileLinksTop);
766 _docSaveAs->show();
767 _docCancel->hide();
768 }
769 }
770 updateDocSize();
771 } else {
772 _docIconRect = QRect(
773 (width() - st::mediaviewFileIconSize) / 2,
774 (height() - st::mediaviewFileIconSize) / 2,
775 st::mediaviewFileIconSize,
776 st::mediaviewFileIconSize);
777 _docDownload->hide();
778 _docSaveAs->hide();
779 _docCancel->hide();
780 }
781 radialStart();
782
783 updateThemePreviewGeometry();
784
785 _saveVisible = contentCanBeSaved();
786 _rotateVisible = !_themePreviewShown;
787 const auto navRect = [&](int i) {
788 return QRect(width() - st::mediaviewIconSize.width() * i,
789 height() - st::mediaviewIconSize.height(),
790 st::mediaviewIconSize.width(),
791 st::mediaviewIconSize.height());
792 };
793 _saveNav = navRect(_rotateVisible ? 3 : 2);
794 _saveNavIcon = style::centerrect(_saveNav, st::mediaviewSave);
795 _rotateNav = navRect(2);
796 _rotateNavIcon = style::centerrect(_rotateNav, st::mediaviewRotate);
797 _moreNav = navRect(1);
798 _moreNavIcon = style::centerrect(_moreNav, st::mediaviewMore);
799
800 const auto dNow = QDateTime::currentDateTime();
801 const auto d = [&] {
802 if (_message) {
803 return ItemDateTime(_message);
804 } else if (_photo) {
805 return base::unixtime::parse(_photo->date);
806 } else if (_document) {
807 return base::unixtime::parse(_document->date);
808 }
809 return dNow;
810 }();
811 _dateText = Ui::FormatDateTime(d, cDateFormat(), cTimeFormat());
812 if (!_fromName.isEmpty()) {
813 _fromNameLabel.setText(st::mediaviewTextStyle, _fromName, Ui::NameTextOptions());
814 _nameNav = QRect(st::mediaviewTextLeft, height() - st::mediaviewTextTop, qMin(_fromNameLabel.maxWidth(), width() / 3), st::mediaviewFont->height);
815 _dateNav = QRect(st::mediaviewTextLeft + _nameNav.width() + st::mediaviewTextSkip, height() - st::mediaviewTextTop, st::mediaviewFont->width(_dateText), st::mediaviewFont->height);
816 } else {
817 _nameNav = QRect();
818 _dateNav = QRect(st::mediaviewTextLeft, height() - st::mediaviewTextTop, st::mediaviewFont->width(_dateText), st::mediaviewFont->height);
819 }
820 updateHeader();
821 refreshNavVisibility();
822 resizeCenteredControls();
823
824 updateOver(_widget->mapFromGlobal(QCursor::pos()));
825 update();
826 }
827
resizeCenteredControls()828 void OverlayWidget::resizeCenteredControls() {
829 const auto bottomSkip = std::max(
830 _dateNav.left() + _dateNav.width(),
831 _headerNav.left() + _headerNav.width())
832 + st::mediaviewCaptionMargin.width();
833 _groupThumbsAvailableWidth = std::max(
834 width() - 2 * bottomSkip,
835 st::msgMinWidth
836 + st::mediaviewCaptionPadding.left()
837 + st::mediaviewCaptionPadding.right());
838 _groupThumbsLeft = (width() - _groupThumbsAvailableWidth) / 2;
839 refreshGroupThumbs();
840 _groupThumbsTop = _groupThumbs ? (height() - _groupThumbs->height()) : 0;
841
842 refreshClipControllerGeometry();
843 refreshCaptionGeometry();
844 }
845
refreshCaptionGeometry()846 void OverlayWidget::refreshCaptionGeometry() {
847 if (_caption.isEmpty()) {
848 _captionRect = QRect();
849 return;
850 }
851
852 if (_groupThumbs && _groupThumbs->hiding()) {
853 _groupThumbs = nullptr;
854 _groupThumbsRect = QRect();
855 }
856 const auto captionBottom = (_streamed && !videoIsGifOrUserpic())
857 ? (_streamed->controls.y() - st::mediaviewCaptionMargin.height())
858 : _groupThumbs
859 ? _groupThumbsTop
860 : height() - st::mediaviewCaptionMargin.height();
861 const auto captionWidth = std::min(
862 _groupThumbsAvailableWidth
863 - st::mediaviewCaptionPadding.left()
864 - st::mediaviewCaptionPadding.right(),
865 _caption.maxWidth());
866 const auto captionHeight = std::min(
867 _caption.countHeight(captionWidth),
868 height() / 4
869 - st::mediaviewCaptionPadding.top()
870 - st::mediaviewCaptionPadding.bottom()
871 - 2 * st::mediaviewCaptionMargin.height());
872 _captionRect = QRect(
873 (width() - captionWidth) / 2,
874 captionBottom
875 - captionHeight
876 - st::mediaviewCaptionPadding.bottom(),
877 captionWidth,
878 captionHeight);
879 }
880
fillContextMenuActions(const MenuCallback & addAction)881 void OverlayWidget::fillContextMenuActions(const MenuCallback &addAction) {
882 if (_document && _document->loading()) {
883 addAction(tr::lng_cancel(tr::now), [=] { saveCancel(); });
884 }
885 if (_message && _message->isRegular()) {
886 addAction(tr::lng_context_to_msg(tr::now), [=] { toMessage(); });
887 }
888 if (_document && !_document->filepath(true).isEmpty()) {
889 const auto text = Platform::IsMac()
890 ? tr::lng_context_show_in_finder(tr::now)
891 : tr::lng_context_show_in_folder(tr::now);
892 addAction(text, [=] { showInFolder(); });
893 }
894 if ((_document && documentContentShown()) || (_photo && _photoMedia->loaded())) {
895 addAction(tr::lng_mediaview_copy(tr::now), [=] { copyMedia(); });
896 }
897 if ((_photo && _photo->hasAttachedStickers())
898 || (_document && _document->hasAttachedStickers())) {
899 addAction(
900 tr::lng_context_attached_stickers(tr::now),
901 [=] { showAttachedStickers(); });
902 }
903 if (_message && _message->allowsForward()) {
904 addAction(tr::lng_mediaview_forward(tr::now), [=] { forwardMedia(); });
905 }
906 const auto canDelete = [&] {
907 if (_message && _message->canDelete()) {
908 return true;
909 } else if (!_message
910 && _photo
911 && _user
912 && _user == _user->session().user()) {
913 return _userPhotosData && _fullIndex && _fullCount;
914 } else if (_photo && _photo->peer && _photo->peer->userpicPhotoId() == _photo->id) {
915 if (auto chat = _photo->peer->asChat()) {
916 return chat->canEditInformation();
917 } else if (auto channel = _photo->peer->asChannel()) {
918 return channel->canEditInformation();
919 }
920 }
921 return false;
922 }();
923 if (canDelete) {
924 addAction(tr::lng_mediaview_delete(tr::now), [=] { deleteMedia(); });
925 }
926 addAction(tr::lng_mediaview_save_as(tr::now), [=] { saveAs(); });
927
928 if (const auto overviewType = computeOverviewType()) {
929 const auto text = _document
930 ? tr::lng_mediaview_files_all(tr::now)
931 : tr::lng_mediaview_photos_all(tr::now);
932 addAction(text, [=] { showMediaOverview(); });
933 }
934 }
935
computeOverviewType() const936 auto OverlayWidget::computeOverviewType() const
937 -> std::optional<SharedMediaType> {
938 if (const auto mediaType = sharedMediaType()) {
939 if (const auto overviewType = SharedMediaOverviewType(*mediaType)) {
940 return overviewType;
941 } else if (mediaType == SharedMediaType::PhotoVideo) {
942 if (_photo) {
943 return SharedMediaOverviewType(SharedMediaType::Photo);
944 } else if (_document) {
945 return SharedMediaOverviewType(SharedMediaType::Video);
946 }
947 }
948 }
949 return std::nullopt;
950 }
951
stateAnimationCallback(crl::time now)952 bool OverlayWidget::stateAnimationCallback(crl::time now) {
953 if (anim::Disabled()) {
954 now += st::mediaviewShowDuration + st::mediaviewHideDuration;
955 }
956 for (auto i = begin(_animations); i != end(_animations);) {
957 const auto [state, started] = *i;
958 updateOverRect(state);
959 const auto dt = float64(now - started) / st::mediaviewFadeDuration;
960 if (dt >= 1) {
961 _animationOpacities.erase(state);
962 i = _animations.erase(i);
963 } else {
964 _animationOpacities[state].update(dt, anim::linear);
965 ++i;
966 }
967 }
968 return !_animations.empty() || updateControlsAnimation(now);
969 }
970
updateControlsAnimation(crl::time now)971 bool OverlayWidget::updateControlsAnimation(crl::time now) {
972 if (_controlsState != ControlsShowing
973 && _controlsState != ControlsHiding) {
974 return false;
975 }
976 const auto duration = (_controlsState == ControlsShowing)
977 ? st::mediaviewShowDuration
978 : st::mediaviewHideDuration;
979 const auto dt = float64(now - _controlsAnimStarted)
980 / duration;
981 if (dt >= 1) {
982 _controlsOpacity.finish();
983 _controlsState = (_controlsState == ControlsShowing)
984 ? ControlsShown
985 : ControlsHidden;
986 updateCursor();
987 } else {
988 _controlsOpacity.update(dt, anim::linear);
989 }
990 const auto toUpdate = QRegion()
991 + (_over == OverLeftNav ? _leftNav : _leftNavIcon)
992 + (_over == OverRightNav ? _rightNav : _rightNavIcon)
993 + (_over == OverClose ? _closeNav : _closeNavIcon)
994 + _saveNavIcon
995 + _rotateNavIcon
996 + _moreNavIcon
997 + _headerNav
998 + _nameNav
999 + _dateNav
1000 + _captionRect.marginsAdded(st::mediaviewCaptionPadding)
1001 + _groupThumbsRect;
1002 update(toUpdate);
1003 return (dt < 1);
1004 }
1005
waitingAnimationCallback()1006 void OverlayWidget::waitingAnimationCallback() {
1007 if (!anim::Disabled()) {
1008 update(radialRect());
1009 }
1010 }
1011
updateCursor()1012 void OverlayWidget::updateCursor() {
1013 setCursor(_controlsState == ControlsHidden
1014 ? Qt::BlankCursor
1015 : (_over == OverNone ? style::cur_default : style::cur_pointer));
1016 }
1017
finalContentRotation() const1018 int OverlayWidget::finalContentRotation() const {
1019 return _streamed
1020 ? ((_rotation + (_streamed
1021 ? _streamed->instance.info().video.rotation
1022 : 0)) % 360)
1023 : _rotation;
1024 }
1025
finalContentRect() const1026 QRect OverlayWidget::finalContentRect() const {
1027 return { _x, _y, _w, _h };
1028 }
1029
contentGeometry() const1030 OverlayWidget::ContentGeometry OverlayWidget::contentGeometry() const {
1031 const auto toRotation = qreal(finalContentRotation());
1032 const auto toRectRotated = QRectF(finalContentRect());
1033 const auto toRectCenter = toRectRotated.center();
1034 const auto toRect = ((int(toRotation) % 180) == 90)
1035 ? QRectF(
1036 toRectCenter.x() - toRectRotated.height() / 2.,
1037 toRectCenter.y() - toRectRotated.width() / 2.,
1038 toRectRotated.height(),
1039 toRectRotated.width())
1040 : toRectRotated;
1041 if (!_geometryAnimation.animating()) {
1042 return { toRect, toRotation };
1043 }
1044 const auto fromRect = _oldGeometry.rect;
1045 const auto fromRotation = _oldGeometry.rotation;
1046 const auto progress = _geometryAnimation.value(1.);
1047 const auto rotationDelta = (toRotation - fromRotation);
1048 const auto useRotationDelta = (rotationDelta > 180.)
1049 ? (rotationDelta - 360.)
1050 : (rotationDelta <= -180.)
1051 ? (rotationDelta + 360.)
1052 : rotationDelta;
1053 const auto rotation = fromRotation + useRotationDelta * progress;
1054 const auto useRotation = (rotation > 360.)
1055 ? (rotation - 360.)
1056 : (rotation < 0.)
1057 ? (rotation + 360.)
1058 : rotation;
1059 const auto useRect = QRectF(
1060 fromRect.x() + (toRect.x() - fromRect.x()) * progress,
1061 fromRect.y() + (toRect.y() - fromRect.y()) * progress,
1062 fromRect.width() + (toRect.width() - fromRect.width()) * progress,
1063 fromRect.height() + (toRect.height() - fromRect.height()) * progress
1064 );
1065 return { useRect, useRotation };
1066 }
1067
updateContentRect()1068 void OverlayWidget::updateContentRect() {
1069 if (_opengl) {
1070 update();
1071 } else {
1072 update(finalContentRect());
1073 }
1074 }
1075
contentSizeChanged()1076 void OverlayWidget::contentSizeChanged() {
1077 _width = _w;
1078 _height = _h;
1079 resizeContentByScreenSize();
1080 }
1081
resizeContentByScreenSize()1082 void OverlayWidget::resizeContentByScreenSize() {
1083 const auto bottom = (!_streamed || videoIsGifOrUserpic())
1084 ? height()
1085 : (_streamed->controls.y()
1086 - st::mediaviewCaptionPadding.bottom()
1087 - st::mediaviewCaptionMargin.height());
1088 const auto skipHeight = (height() - bottom);
1089 const auto availableWidth = width();
1090 const auto availableHeight = height() - 2 * skipHeight;
1091 const auto countZoomFor = [&](int outerw, int outerh) {
1092 auto result = float64(outerw) / _width;
1093 if (_height * result > outerh) {
1094 result = float64(outerh) / _height;
1095 }
1096 if (result >= 1.) {
1097 result -= 1.;
1098 } else {
1099 result = 1. - (1. / result);
1100 }
1101 return result;
1102 };
1103 if (_width > 0 && _height > 0) {
1104 _zoomToDefault = countZoomFor(availableWidth, availableHeight);
1105 _zoomToScreen = countZoomFor(width(), height());
1106 } else {
1107 _zoomToDefault = _zoomToScreen = 0;
1108 }
1109 const auto usew = _fullScreenVideo ? width() : availableWidth;
1110 const auto useh = _fullScreenVideo ? height() : availableHeight;
1111 if ((_width > usew) || (_height > useh) || _fullScreenVideo) {
1112 const auto use = _fullScreenVideo ? _zoomToScreen : _zoomToDefault;
1113 _zoom = kZoomToScreenLevel;
1114 if (use >= 0) {
1115 _w = qRound(_width * (use + 1));
1116 _h = qRound(_height * (use + 1));
1117 } else {
1118 _w = qRound(_width / (-use + 1));
1119 _h = qRound(_height / (-use + 1));
1120 }
1121 } else {
1122 _zoom = 0;
1123 _w = _width;
1124 _h = _height;
1125 }
1126 _x = (width() - _w) / 2;
1127 _y = (height() - _h) / 2;
1128 _geometryAnimation.stop();
1129 }
1130
radialProgress() const1131 float64 OverlayWidget::radialProgress() const {
1132 if (_document) {
1133 return _documentMedia->progress();
1134 } else if (_photo) {
1135 return _photoMedia->progress();
1136 }
1137 return 1.;
1138 }
1139
radialLoading() const1140 bool OverlayWidget::radialLoading() const {
1141 if (_streamed) {
1142 return false;
1143 } else if (_document) {
1144 return _document->loading();
1145 } else if (_photo) {
1146 return _photo->displayLoading();
1147 }
1148 return false;
1149 }
1150
radialRect() const1151 QRect OverlayWidget::radialRect() const {
1152 if (_photo) {
1153 return _photoRadialRect;
1154 } else if (_document) {
1155 return QRect(
1156 QPoint(
1157 _docIconRect.x() + ((_docIconRect.width() - st::radialSize.width()) / 2),
1158 _docIconRect.y() + ((_docIconRect.height() - st::radialSize.height()) / 2)),
1159 st::radialSize);
1160 }
1161 return QRect();
1162 }
1163
radialStart()1164 void OverlayWidget::radialStart() {
1165 if (radialLoading() && !_radial.animating()) {
1166 _radial.start(radialProgress());
1167 if (auto shift = radialTimeShift()) {
1168 _radial.update(radialProgress(), !radialLoading(), crl::now() + shift);
1169 }
1170 }
1171 }
1172
radialTimeShift() const1173 crl::time OverlayWidget::radialTimeShift() const {
1174 return _photo ? st::radialDuration : 0;
1175 }
1176
radialAnimationCallback(crl::time now)1177 bool OverlayWidget::radialAnimationCallback(crl::time now) {
1178 if ((!_document && !_photo) || _streamed) {
1179 return false;
1180 }
1181 const auto wasAnimating = _radial.animating();
1182 const auto updated = _radial.update(
1183 radialProgress(),
1184 !radialLoading(),
1185 now + radialTimeShift());
1186 if ((wasAnimating || _radial.animating())
1187 && (!anim::Disabled() || updated)) {
1188 update(radialRect());
1189 }
1190 const auto ready = _document && _documentMedia->loaded();
1191 const auto streamVideo = ready && _documentMedia->canBePlayed();
1192 const auto tryOpenImage = ready
1193 && (_document->size < Images::kReadBytesLimit);
1194 if (ready && ((tryOpenImage && !_radial.animating()) || streamVideo)) {
1195 _streamingStartPaused = false;
1196 if (streamVideo) {
1197 redisplayContent();
1198 } else {
1199 auto &location = _document->location(true);
1200 if (location.accessEnable()) {
1201 if (_document->isTheme()
1202 || QImageReader(location.name()).canRead()) {
1203 redisplayContent();
1204 }
1205 location.accessDisable();
1206 }
1207 }
1208 }
1209 return true;
1210 }
1211
zoomIn()1212 void OverlayWidget::zoomIn() {
1213 auto newZoom = _zoom;
1214 const auto full = _fullScreenVideo ? _zoomToScreen : _zoomToDefault;
1215 if (newZoom == kZoomToScreenLevel) {
1216 if (qCeil(full) <= kMaxZoomLevel) {
1217 newZoom = qCeil(full);
1218 }
1219 } else {
1220 if (newZoom < full && (newZoom + 1 > full || (full > kMaxZoomLevel && newZoom == kMaxZoomLevel))) {
1221 newZoom = kZoomToScreenLevel;
1222 } else if (newZoom < kMaxZoomLevel) {
1223 ++newZoom;
1224 }
1225 }
1226 zoomUpdate(newZoom);
1227 }
1228
zoomOut()1229 void OverlayWidget::zoomOut() {
1230 auto newZoom = _zoom;
1231 const auto full = _fullScreenVideo ? _zoomToScreen : _zoomToDefault;
1232 if (newZoom == kZoomToScreenLevel) {
1233 if (qFloor(full) >= -kMaxZoomLevel) {
1234 newZoom = qFloor(full);
1235 }
1236 } else {
1237 if (newZoom > full && (newZoom - 1 < full || (full < -kMaxZoomLevel && newZoom == -kMaxZoomLevel))) {
1238 newZoom = kZoomToScreenLevel;
1239 } else if (newZoom > -kMaxZoomLevel) {
1240 --newZoom;
1241 }
1242 }
1243 zoomUpdate(newZoom);
1244 }
1245
zoomReset()1246 void OverlayWidget::zoomReset() {
1247 auto newZoom = _zoom;
1248 const auto full = _fullScreenVideo ? _zoomToScreen : _zoomToDefault;
1249 if (_zoom == 0) {
1250 if (qFloor(full) == qCeil(full) && qRound(full) >= -kMaxZoomLevel && qRound(full) <= kMaxZoomLevel) {
1251 newZoom = qRound(full);
1252 } else {
1253 newZoom = kZoomToScreenLevel;
1254 }
1255 } else {
1256 newZoom = 0;
1257 }
1258 _x = -_width / 2;
1259 _y = -_height / 2;
1260 float64 z = (_zoom == kZoomToScreenLevel) ? full : _zoom;
1261 if (z >= 0) {
1262 _x = qRound(_x * (z + 1));
1263 _y = qRound(_y * (z + 1));
1264 } else {
1265 _x = qRound(_x / (-z + 1));
1266 _y = qRound(_y / (-z + 1));
1267 }
1268 _x += width() / 2;
1269 _y += height() / 2;
1270 update();
1271 zoomUpdate(newZoom);
1272 }
1273
zoomUpdate(int32 & newZoom)1274 void OverlayWidget::zoomUpdate(int32 &newZoom) {
1275 if (newZoom != kZoomToScreenLevel) {
1276 while ((newZoom < 0 && (-newZoom + 1) > _w) || (-newZoom + 1) > _h) {
1277 ++newZoom;
1278 }
1279 }
1280 setZoomLevel(newZoom);
1281 }
1282
clearSession()1283 void OverlayWidget::clearSession() {
1284 if (!isHidden()) {
1285 hide();
1286 }
1287 _sessionLifetime.destroy();
1288 if (!_animations.empty()) {
1289 _animations.clear();
1290 _stateAnimation.stop();
1291 }
1292 if (!_animationOpacities.empty()) {
1293 _animationOpacities.clear();
1294 }
1295 clearStreaming();
1296 setContext(v::null);
1297 _from = nullptr;
1298 _fromName = QString();
1299 assignMediaPointer(nullptr);
1300 _fullScreenVideo = false;
1301 _caption.clear();
1302 _sharedMedia = nullptr;
1303 _userPhotos = nullptr;
1304 _collage = nullptr;
1305 _session = nullptr;
1306 }
1307
~OverlayWidget()1308 OverlayWidget::~OverlayWidget() {
1309 clearSession();
1310 }
1311
assignMediaPointer(DocumentData * document)1312 void OverlayWidget::assignMediaPointer(DocumentData *document) {
1313 _savePhotoVideoWhenLoaded = SavePhotoVideo::None;
1314 _photo = nullptr;
1315 _photoMedia = nullptr;
1316 if (_document != document) {
1317 if ((_document = document)) {
1318 _documentMedia = _document->createMediaView();
1319 _documentMedia->goodThumbnailWanted();
1320 _documentMedia->thumbnailWanted(fileOrigin());
1321 } else {
1322 _documentMedia = nullptr;
1323 }
1324 }
1325 }
1326
assignMediaPointer(not_null<PhotoData * > photo)1327 void OverlayWidget::assignMediaPointer(not_null<PhotoData*> photo) {
1328 _savePhotoVideoWhenLoaded = SavePhotoVideo::None;
1329 _document = nullptr;
1330 _documentMedia = nullptr;
1331 if (_photo != photo) {
1332 _photo = photo;
1333 _photoMedia = _photo->createMediaView();
1334 _photoMedia->wanted(Data::PhotoSize::Small, fileOrigin());
1335 if (!_photo->hasVideo() || _photo->videoPlaybackFailed()) {
1336 _photo->load(fileOrigin(), LoadFromCloudOrLocal, true);
1337 }
1338 }
1339 }
1340
clickHandlerActiveChanged(const ClickHandlerPtr & p,bool active)1341 void OverlayWidget::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
1342 setCursor((active || ClickHandler::getPressed()) ? style::cur_pointer : style::cur_default);
1343 update(QRegion(_saveMsg) + _captionRect);
1344 }
1345
clickHandlerPressedChanged(const ClickHandlerPtr & p,bool pressed)1346 void OverlayWidget::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) {
1347 setCursor((pressed || ClickHandler::getActive()) ? style::cur_pointer : style::cur_default);
1348 update(QRegion(_saveMsg) + _captionRect);
1349 }
1350
lifetime()1351 rpl::lifetime &OverlayWidget::lifetime() {
1352 return _surface->lifetime();
1353 }
1354
showSaveMsgFile()1355 void OverlayWidget::showSaveMsgFile() {
1356 File::ShowInFolder(_saveMsgFilename);
1357 }
1358
close()1359 void OverlayWidget::close() {
1360 Core::App().hideMediaView();
1361 }
1362
activateControls()1363 void OverlayWidget::activateControls() {
1364 if (!_menu && !_mousePressed) {
1365 _controlsHideTimer.callOnce(st::mediaviewWaitHide);
1366 }
1367 if (_fullScreenVideo) {
1368 if (_streamed) {
1369 _streamed->controls.showAnimated();
1370 }
1371 }
1372 if (_controlsState == ControlsHiding || _controlsState == ControlsHidden) {
1373 _controlsState = ControlsShowing;
1374 _controlsAnimStarted = crl::now();
1375 _controlsOpacity.start(1);
1376 if (!_stateAnimation.animating()) {
1377 _stateAnimation.start();
1378 }
1379 }
1380 }
1381
hideControls(bool force)1382 void OverlayWidget::hideControls(bool force) {
1383 if (!force) {
1384 if (!_dropdown->isHidden()
1385 || (_streamed && _streamed->controls.hasMenu())
1386 || _menu
1387 || _mousePressed
1388 || (_fullScreenVideo
1389 && !videoIsGifOrUserpic()
1390 && _streamed->controls.geometry().contains(_lastMouseMovePos))) {
1391 return;
1392 }
1393 }
1394 if (_fullScreenVideo) {
1395 _streamed->controls.hideAnimated();
1396 }
1397 if (_controlsState == ControlsHiding || _controlsState == ControlsHidden) return;
1398
1399 _lastMouseMovePos = _widget->mapFromGlobal(QCursor::pos());
1400 _controlsState = ControlsHiding;
1401 _controlsAnimStarted = crl::now();
1402 _controlsOpacity.start(0);
1403 if (!_stateAnimation.animating()) {
1404 _stateAnimation.start();
1405 }
1406 }
1407
dropdownHidden()1408 void OverlayWidget::dropdownHidden() {
1409 setFocus();
1410 _ignoringDropdown = true;
1411 _lastMouseMovePos = _widget->mapFromGlobal(QCursor::pos());
1412 updateOver(_lastMouseMovePos);
1413 _ignoringDropdown = false;
1414 if (!_controlsHideTimer.isActive()) {
1415 hideControls(true);
1416 }
1417 }
1418
handleScreenChanged(QScreen * screen)1419 void OverlayWidget::handleScreenChanged(QScreen *screen) {
1420 subscribeToScreenGeometry();
1421 if (isHidden()) {
1422 return;
1423 }
1424
1425 const auto screenList = QGuiApplication::screens();
1426 DEBUG_LOG(("Viewer Pos: Screen changed to: %1")
1427 .arg(screenList.indexOf(screen)));
1428
1429 moveToScreen();
1430 }
1431
subscribeToScreenGeometry()1432 void OverlayWidget::subscribeToScreenGeometry() {
1433 _screenGeometryLifetime.destroy();
1434 const auto screen = window()->screen();
1435 if (!screen) {
1436 return;
1437 }
1438 base::qt_signal_producer(
1439 screen,
1440 &QScreen::geometryChanged
1441 ) | rpl::start_with_next([=] {
1442 updateGeometry();
1443 }, _screenGeometryLifetime);
1444 }
1445
toMessage()1446 void OverlayWidget::toMessage() {
1447 if (const auto item = _message) {
1448 close();
1449 if (const auto window = findWindow()) {
1450 window->showPeerHistoryAtItem(item);
1451 }
1452 }
1453 }
1454
notifyFileDialogShown(bool shown)1455 void OverlayWidget::notifyFileDialogShown(bool shown) {
1456 if (shown && isHidden()) {
1457 return;
1458 }
1459 if (shown) {
1460 Ui::Platform::BringToBack(_widget);
1461 } else {
1462 Ui::Platform::ShowOverAll(_widget);
1463 }
1464 }
1465
saveAs()1466 void OverlayWidget::saveAs() {
1467 QString file;
1468 if (_document) {
1469 const auto &location = _document->location(true);
1470 const auto bytes = _documentMedia->bytes();
1471 if (!bytes.isEmpty() || location.accessEnable()) {
1472 QFileInfo alreadyInfo(location.name());
1473 QDir alreadyDir(alreadyInfo.dir());
1474 QString name = alreadyInfo.fileName(), filter;
1475 const auto mimeType = Core::MimeTypeForName(_document->mimeString());
1476 QStringList p = mimeType.globPatterns();
1477 QString pattern = p.isEmpty() ? QString() : p.front();
1478 if (name.isEmpty()) {
1479 name = pattern.isEmpty() ? qsl(".unknown") : pattern.replace('*', QString());
1480 }
1481
1482 if (pattern.isEmpty()) {
1483 filter = QString();
1484 } else {
1485 filter = mimeType.filterString() + qsl(";;") + FileDialog::AllFilesFilter();
1486 }
1487
1488 file = FileNameForSave(
1489 _session,
1490 tr::lng_save_file(tr::now),
1491 filter,
1492 qsl("doc"),
1493 name,
1494 true,
1495 alreadyDir);
1496 if (!file.isEmpty() && file != location.name()) {
1497 if (bytes.isEmpty()) {
1498 QFile(file).remove();
1499 QFile(location.name()).copy(file);
1500 } else {
1501 QFile f(file);
1502 f.open(QIODevice::WriteOnly);
1503 f.write(bytes);
1504 }
1505 }
1506
1507 if (bytes.isEmpty()) {
1508 location.accessDisable();
1509 }
1510 } else {
1511 DocumentSaveClickHandler::Save(
1512 fileOrigin(),
1513 _document,
1514 DocumentSaveClickHandler::Mode::ToNewFile);
1515 updateControls();
1516 updateOver(_lastMouseMovePos);
1517 }
1518 } else if (_photo && _photo->hasVideo()) {
1519 if (const auto bytes = _photoMedia->videoContent(); !bytes.isEmpty()) {
1520 const auto photo = _photo;
1521 auto filter = qsl("Video Files (*.mp4);;") + FileDialog::AllFilesFilter();
1522 FileDialog::GetWritePath(
1523 _widget.get(),
1524 tr::lng_save_video(tr::now),
1525 filter,
1526 filedialogDefaultName(
1527 qsl("photo"),
1528 qsl(".mp4"),
1529 QString(),
1530 false,
1531 _photo->date),
1532 crl::guard(_widget, [=](const QString &result) {
1533 QFile f(result);
1534 if (!result.isEmpty()
1535 && _photo == photo
1536 && f.open(QIODevice::WriteOnly)) {
1537 f.write(bytes);
1538 }
1539 }));
1540 } else {
1541 _photo->loadVideo(fileOrigin());
1542 _savePhotoVideoWhenLoaded = SavePhotoVideo::SaveAs;
1543 }
1544 } else {
1545 if (!_photo || !_photoMedia->loaded()) {
1546 return;
1547 }
1548
1549 const auto image = _photoMedia->image(Data::PhotoSize::Large)->original();
1550 const auto photo = _photo;
1551 auto filter = qsl("JPEG Image (*.jpg);;") + FileDialog::AllFilesFilter();
1552 FileDialog::GetWritePath(
1553 _widget.get(),
1554 tr::lng_save_photo(tr::now),
1555 filter,
1556 filedialogDefaultName(
1557 qsl("photo"),
1558 qsl(".jpg"),
1559 QString(),
1560 false,
1561 _photo->date),
1562 crl::guard(_widget, [=](const QString &result) {
1563 if (!result.isEmpty() && _photo == photo) {
1564 image.save(result, "JPG");
1565 }
1566 }));
1567 }
1568 activate();
1569 }
1570
handleDocumentClick()1571 void OverlayWidget::handleDocumentClick() {
1572 if (_document->loading()) {
1573 saveCancel();
1574 } else {
1575 Data::ResolveDocument(findWindow(), _document, _message);
1576 if (_document->loading() && !_radial.animating()) {
1577 _radial.start(_documentMedia->progress());
1578 }
1579 }
1580 }
1581
downloadMedia()1582 void OverlayWidget::downloadMedia() {
1583 if (!_photo && !_document) {
1584 return;
1585 }
1586 if (Core::App().settings().askDownloadPath()) {
1587 return saveAs();
1588 }
1589
1590 QString path;
1591 const auto session = _photo ? &_photo->session() : &_document->session();
1592 if (Core::App().settings().downloadPath().isEmpty()) {
1593 path = File::DefaultDownloadPath(session);
1594 } else if (Core::App().settings().downloadPath() == qsl("tmp")) {
1595 path = session->local().tempDirectory();
1596 } else {
1597 path = Core::App().settings().downloadPath();
1598 }
1599 QString toName;
1600 if (_document) {
1601 const auto &location = _document->location(true);
1602 if (location.accessEnable()) {
1603 if (!QDir().exists(path)) QDir().mkpath(path);
1604 toName = filedialogNextFilename(
1605 _document->filename(),
1606 location.name(),
1607 path);
1608 if (!toName.isEmpty() && toName != location.name()) {
1609 QFile(toName).remove();
1610 if (!QFile(location.name()).copy(toName)) {
1611 toName = QString();
1612 }
1613 }
1614 location.accessDisable();
1615 } else {
1616 if (_document->filepath(true).isEmpty()
1617 && !_document->loading()) {
1618 DocumentSaveClickHandler::Save(
1619 fileOrigin(),
1620 _document,
1621 DocumentSaveClickHandler::Mode::ToFile);
1622 updateControls();
1623 } else {
1624 _saveVisible = contentCanBeSaved();
1625 update(_saveNav);
1626 }
1627 updateOver(_lastMouseMovePos);
1628 }
1629 } else if (_photo && _photo->hasVideo()) {
1630 if (const auto bytes = _photoMedia->videoContent(); !bytes.isEmpty()) {
1631 if (!QDir().exists(path)) {
1632 QDir().mkpath(path);
1633 }
1634 toName = filedialogDefaultName(qsl("photo"), qsl(".mp4"), path);
1635 QFile f(toName);
1636 if (!f.open(QIODevice::WriteOnly)
1637 || f.write(bytes) != bytes.size()) {
1638 toName = QString();
1639 }
1640 } else {
1641 _photo->loadVideo(fileOrigin());
1642 _savePhotoVideoWhenLoaded = SavePhotoVideo::QuickSave;
1643 }
1644 } else {
1645 if (!_photo || !_photoMedia->loaded()) {
1646 _saveVisible = contentCanBeSaved();
1647 update(_saveNav);
1648 } else {
1649 const auto image = _photoMedia->image(
1650 Data::PhotoSize::Large)->original();
1651
1652 if (!QDir().exists(path)) {
1653 QDir().mkpath(path);
1654 }
1655 toName = filedialogDefaultName(qsl("photo"), qsl(".jpg"), path);
1656 if (!image.save(toName, "JPG")) {
1657 toName = QString();
1658 }
1659 }
1660 }
1661 if (!toName.isEmpty()) {
1662 _saveMsgFilename = toName;
1663 _saveMsgStarted = crl::now();
1664 _saveMsgOpacity.start(1);
1665 updateImage();
1666 }
1667 }
1668
saveCancel()1669 void OverlayWidget::saveCancel() {
1670 if (_document && _document->loading()) {
1671 _document->cancel();
1672 if (_documentMedia->canBePlayed()) {
1673 redisplayContent();
1674 }
1675 }
1676 }
1677
showInFolder()1678 void OverlayWidget::showInFolder() {
1679 if (!_document) return;
1680
1681 auto filepath = _document->filepath(true);
1682 if (!filepath.isEmpty()) {
1683 File::ShowInFolder(filepath);
1684 close();
1685 }
1686 }
1687
forwardMedia()1688 void OverlayWidget::forwardMedia() {
1689 if (!_session) {
1690 return;
1691 }
1692 const auto &active = _session->windows();
1693 if (active.empty()) {
1694 return;
1695 }
1696 const auto id = (_message && _message->allowsForward())
1697 ? _message->fullId()
1698 : FullMsgId();
1699 if (id) {
1700 close();
1701 Window::ShowForwardMessagesBox(active.front(), { 1, id });
1702 }
1703 }
1704
deleteMedia()1705 void OverlayWidget::deleteMedia() {
1706 if (!_session) {
1707 return;
1708 }
1709
1710 const auto session = _session;
1711 const auto photo = _photo;
1712 const auto message = _message;
1713 const auto deletingPeerPhoto = [&] {
1714 if (!_message) {
1715 return true;
1716 } else if (_photo && _history) {
1717 if (_history->peer->userpicPhotoId() == _photo->id) {
1718 return _firstOpenedPeerPhoto;
1719 }
1720 }
1721 return false;
1722 }();
1723 close();
1724
1725 if (const auto window = findWindow()) {
1726 if (deletingPeerPhoto) {
1727 if (photo) {
1728 window->show(
1729 Box<Ui::ConfirmBox>(
1730 tr::lng_delete_photo_sure(tr::now),
1731 tr::lng_box_delete(tr::now),
1732 crl::guard(_widget, [=] {
1733 session->api().peerPhoto().clear(photo);
1734 Ui::hideLayer();
1735 })),
1736 Ui::LayerOption::CloseOther);
1737 }
1738 } else if (message) {
1739 const auto suggestModerateActions = true;
1740 window->show(
1741 Box<DeleteMessagesBox>(message, suggestModerateActions),
1742 Ui::LayerOption::CloseOther);
1743 }
1744 }
1745 }
1746
showMediaOverview()1747 void OverlayWidget::showMediaOverview() {
1748 if (_menu) {
1749 _menu->hideMenu(true);
1750 }
1751 update();
1752 if (const auto overviewType = computeOverviewType()) {
1753 close();
1754 SharedMediaShowOverview(*overviewType, _history);
1755 }
1756 }
1757
copyMedia()1758 void OverlayWidget::copyMedia() {
1759 _dropdown->hideAnimated(Ui::DropdownMenu::HideOption::IgnoreShow);
1760 if (_document) {
1761 QGuiApplication::clipboard()->setImage(transformedShownContent());
1762 } else if (_photo && _photoMedia->loaded()) {
1763 const auto image = _photoMedia->image(
1764 Data::PhotoSize::Large)->original();
1765 QGuiApplication::clipboard()->setImage(image);
1766 }
1767 }
1768
showAttachedStickers()1769 void OverlayWidget::showAttachedStickers() {
1770 if (!_session) {
1771 return;
1772 }
1773 const auto &active = _session->windows();
1774 if (active.empty()) {
1775 return;
1776 }
1777 const auto window = active.front();
1778 auto &attachedStickers = _session->api().attachedStickers();
1779 if (_photo) {
1780 attachedStickers.requestAttachedStickerSets(window, _photo);
1781 } else if (_document) {
1782 attachedStickers.requestAttachedStickerSets(window, _document);
1783 } else {
1784 return;
1785 }
1786 close();
1787 }
1788
sharedMediaType() const1789 auto OverlayWidget::sharedMediaType() const
1790 -> std::optional<SharedMediaType> {
1791 using Type = SharedMediaType;
1792 if (_message) {
1793 if (const auto media = _message->media()) {
1794 if (media->webpage()) {
1795 return std::nullopt;
1796 }
1797 }
1798 if (_photo) {
1799 if (_message->isService()) {
1800 return Type::ChatPhoto;
1801 }
1802 return Type::PhotoVideo;
1803 } else if (_document) {
1804 if (_document->isGifv()) {
1805 return Type::GIF;
1806 } else if (_document->isVideoFile()) {
1807 return Type::PhotoVideo;
1808 }
1809 return Type::File;
1810 }
1811 }
1812 return std::nullopt;
1813 }
1814
sharedMediaKey() const1815 auto OverlayWidget::sharedMediaKey() const -> std::optional<SharedMediaKey> {
1816 if (!_message
1817 && _peer
1818 && !_user
1819 && _photo
1820 && _peer->userpicPhotoId() == _photo->id) {
1821 return SharedMediaKey {
1822 _history->peer->id,
1823 _migrated ? _migrated->peer->id : 0,
1824 SharedMediaType::ChatPhoto,
1825 _photo
1826 };
1827 }
1828 if (!_message) {
1829 return std::nullopt;
1830 }
1831 const auto isScheduled = _message->isScheduled();
1832 const auto keyForType = [&](SharedMediaType type) -> SharedMediaKey {
1833 return {
1834 _history->peer->id,
1835 _migrated ? _migrated->peer->id : 0,
1836 type,
1837 (_message->history() == _history
1838 ? _message->id
1839 : (_message->id - ServerMaxMsgId)),
1840 isScheduled
1841 };
1842 };
1843 if (!_message->isRegular() && !isScheduled) {
1844 return std::nullopt;
1845 }
1846 return sharedMediaType() | keyForType;
1847 }
1848
fileOrigin() const1849 Data::FileOrigin OverlayWidget::fileOrigin() const {
1850 if (_message) {
1851 return _message->fullId();
1852 } else if (_photo && _user) {
1853 return Data::FileOriginUserPhoto(peerToUser(_user->id), _photo->id);
1854 } else if (_photo && _peer && _peer->userpicPhotoId() == _photo->id) {
1855 return Data::FileOriginPeerPhoto(_peer->id);
1856 }
1857 return Data::FileOrigin();
1858 }
1859
fileOrigin(const Entity & entity) const1860 Data::FileOrigin OverlayWidget::fileOrigin(const Entity &entity) const {
1861 if (const auto item = entity.item) {
1862 return item->fullId();
1863 } else if (!v::is<not_null<PhotoData*>>(entity.data)) {
1864 return Data::FileOrigin();
1865 }
1866 const auto photo = v::get<not_null<PhotoData*>>(entity.data);
1867 if (_user) {
1868 return Data::FileOriginUserPhoto(peerToUser(_user->id), photo->id);
1869 } else if (_peer && _peer->userpicPhotoId() == photo->id) {
1870 return Data::FileOriginPeerPhoto(_peer->id);
1871 }
1872 return Data::FileOrigin();
1873 }
1874
validSharedMedia() const1875 bool OverlayWidget::validSharedMedia() const {
1876 if (auto key = sharedMediaKey()) {
1877 if (!_sharedMedia) {
1878 return false;
1879 }
1880 using Key = SharedMediaWithLastSlice::Key;
1881 auto inSameDomain = [](const Key &a, const Key &b) {
1882 return (a.type == b.type)
1883 && (a.peerId == b.peerId)
1884 && (a.migratedPeerId == b.migratedPeerId)
1885 && (a.scheduled == b.scheduled);
1886 };
1887 auto countDistanceInData = [&](const Key &a, const Key &b) {
1888 return [&](const SharedMediaWithLastSlice &data) {
1889 return inSameDomain(a, b)
1890 ? data.distance(a, b)
1891 : std::optional<int>();
1892 };
1893 };
1894
1895 if (key == _sharedMedia->key) {
1896 return true;
1897 } else if (!_sharedMediaDataKey
1898 || _sharedMedia->key != *_sharedMediaDataKey) {
1899 return false;
1900 }
1901 auto distance = _sharedMediaData
1902 | countDistanceInData(*key, _sharedMedia->key)
1903 | func::abs;
1904 if (distance) {
1905 return (*distance < kIdsPreloadAfter);
1906 }
1907 }
1908 return (_sharedMedia == nullptr);
1909 }
1910
validateSharedMedia()1911 void OverlayWidget::validateSharedMedia() {
1912 if (const auto key = sharedMediaKey()) {
1913 Assert(_history != nullptr);
1914
1915 _sharedMedia = std::make_unique<SharedMedia>(*key);
1916 auto viewer = (key->type == SharedMediaType::ChatPhoto)
1917 ? SharedMediaWithLastReversedViewer
1918 : SharedMediaWithLastViewer;
1919 viewer(
1920 &_history->session(),
1921 *key,
1922 kIdsLimit,
1923 kIdsLimit
1924 ) | rpl::start_with_next([this](
1925 SharedMediaWithLastSlice &&update) {
1926 handleSharedMediaUpdate(std::move(update));
1927 }, _sharedMedia->lifetime);
1928 } else {
1929 _sharedMedia = nullptr;
1930 _sharedMediaData = std::nullopt;
1931 _sharedMediaDataKey = std::nullopt;
1932 }
1933 }
1934
handleSharedMediaUpdate(SharedMediaWithLastSlice && update)1935 void OverlayWidget::handleSharedMediaUpdate(SharedMediaWithLastSlice &&update) {
1936 if ((!_photo && !_document) || !_sharedMedia) {
1937 _sharedMediaData = std::nullopt;
1938 _sharedMediaDataKey = std::nullopt;
1939 } else {
1940 _sharedMediaData = std::move(update);
1941 _sharedMediaDataKey = _sharedMedia->key;
1942 }
1943 findCurrent();
1944 updateControls();
1945 preloadData(0);
1946 }
1947
userPhotosKey() const1948 std::optional<OverlayWidget::UserPhotosKey> OverlayWidget::userPhotosKey() const {
1949 if (!_message && _user && _photo) {
1950 return UserPhotosKey{ peerToUser(_user->id), _photo->id };
1951 }
1952 return std::nullopt;
1953 }
1954
validUserPhotos() const1955 bool OverlayWidget::validUserPhotos() const {
1956 if (const auto key = userPhotosKey()) {
1957 if (!_userPhotos) {
1958 return false;
1959 }
1960 const auto countDistanceInData = [](const auto &a, const auto &b) {
1961 return [&](const UserPhotosSlice &data) {
1962 return data.distance(a, b);
1963 };
1964 };
1965
1966 const auto distance = (key == _userPhotos->key) ? 0 :
1967 _userPhotosData
1968 | countDistanceInData(*key, _userPhotos->key)
1969 | func::abs;
1970 if (distance) {
1971 return (*distance < kIdsPreloadAfter);
1972 }
1973 }
1974 return (_userPhotos == nullptr);
1975 }
1976
validateUserPhotos()1977 void OverlayWidget::validateUserPhotos() {
1978 if (const auto key = userPhotosKey()) {
1979 Assert(_user != nullptr);
1980
1981 _userPhotos = std::make_unique<UserPhotos>(*key);
1982 UserPhotosReversedViewer(
1983 &_user->session(),
1984 *key,
1985 kIdsLimit,
1986 kIdsLimit
1987 ) | rpl::start_with_next([this](
1988 UserPhotosSlice &&update) {
1989 handleUserPhotosUpdate(std::move(update));
1990 }, _userPhotos->lifetime);
1991 } else {
1992 _userPhotos = nullptr;
1993 _userPhotosData = std::nullopt;
1994 }
1995 }
1996
handleUserPhotosUpdate(UserPhotosSlice && update)1997 void OverlayWidget::handleUserPhotosUpdate(UserPhotosSlice &&update) {
1998 if (!_photo || !_userPhotos) {
1999 _userPhotosData = std::nullopt;
2000 } else {
2001 _userPhotosData = std::move(update);
2002 }
2003 findCurrent();
2004 updateControls();
2005 preloadData(0);
2006 }
2007
collageKey() const2008 std::optional<OverlayWidget::CollageKey> OverlayWidget::collageKey() const {
2009 if (_message) {
2010 if (const auto media = _message->media()) {
2011 if (const auto page = media->webpage()) {
2012 for (const auto &item : page->collage.items) {
2013 if (item == _photo || item == _document) {
2014 return item;
2015 }
2016 }
2017 }
2018 }
2019 }
2020 return std::nullopt;
2021 }
2022
validCollage() const2023 bool OverlayWidget::validCollage() const {
2024 if (const auto key = collageKey()) {
2025 if (!_collage) {
2026 return false;
2027 }
2028
2029 if (key == _collage->key) {
2030 return true;
2031 } else if (_collageData) {
2032 const auto &items = _collageData->items;
2033 if (ranges::find(items, *key) != end(items)
2034 && ranges::find(items, _collage->key) != end(items)) {
2035 return true;
2036 }
2037 }
2038 }
2039 return (_collage == nullptr);
2040 }
2041
validateCollage()2042 void OverlayWidget::validateCollage() {
2043 if (const auto key = collageKey()) {
2044 _collage = std::make_unique<Collage>(*key);
2045 _collageData = WebPageCollage();
2046 if (_message) {
2047 if (const auto media = _message->media()) {
2048 if (const auto page = media->webpage()) {
2049 _collageData = page->collage;
2050 }
2051 }
2052 }
2053 } else {
2054 _collage = nullptr;
2055 _collageData = std::nullopt;
2056 }
2057 }
2058
refreshMediaViewer()2059 void OverlayWidget::refreshMediaViewer() {
2060 if (!validSharedMedia()) {
2061 validateSharedMedia();
2062 }
2063 if (!validUserPhotos()) {
2064 validateUserPhotos();
2065 }
2066 if (!validCollage()) {
2067 validateCollage();
2068 }
2069 findCurrent();
2070 updateControls();
2071 }
2072
refreshFromLabel()2073 void OverlayWidget::refreshFromLabel() {
2074 if (_message) {
2075 _from = _message->senderOriginal();
2076 if (const auto info = _message->hiddenForwardedInfo()) {
2077 _fromName = info->name;
2078 } else {
2079 Assert(_from != nullptr);
2080 const auto from = _from->migrateTo() ? _from->migrateTo() : _from;
2081 _fromName = from->name;
2082 }
2083 } else {
2084 _from = _user;
2085 _fromName = _user ? _user->name : QString();
2086 }
2087 }
2088
refreshCaption()2089 void OverlayWidget::refreshCaption() {
2090 _caption = Ui::Text::String();
2091 if (!_message) {
2092 return;
2093 } else if (const auto media = _message->media()) {
2094 if (media->webpage()) {
2095 return;
2096 }
2097 }
2098 const auto caption = _message->originalText();
2099 if (caption.text.isEmpty()) {
2100 return;
2101 }
2102
2103 using namespace HistoryView;
2104 _caption = Ui::Text::String(st::msgMinWidth);
2105 const auto duration = (_streamed && _document && !videoIsGifOrUserpic())
2106 ? _document->getDuration()
2107 : 0;
2108 const auto base = duration
2109 ? DocumentTimestampLinkBase(_document, _message->fullId())
2110 : QString();
2111 const auto context = Core::MarkedTextContext{
2112 .session = &_message->history()->session()
2113 };
2114 _caption.setMarkedText(
2115 st::mediaviewCaptionStyle,
2116 AddTimestampLinks(caption, duration, base),
2117 Ui::ItemTextOptions(_message),
2118 context);
2119 }
2120
refreshGroupThumbs()2121 void OverlayWidget::refreshGroupThumbs() {
2122 const auto existed = (_groupThumbs != nullptr);
2123 if (_index && _sharedMediaData) {
2124 View::GroupThumbs::Refresh(
2125 _session,
2126 _groupThumbs,
2127 *_sharedMediaData,
2128 *_index,
2129 _groupThumbsAvailableWidth);
2130 } else if (_index && _userPhotosData) {
2131 View::GroupThumbs::Refresh(
2132 _session,
2133 _groupThumbs,
2134 *_userPhotosData,
2135 *_index,
2136 _groupThumbsAvailableWidth);
2137 } else if (_index && _collageData) {
2138 const auto messageId = _message ? _message->fullId() : FullMsgId();
2139 View::GroupThumbs::Refresh(
2140 _session,
2141 _groupThumbs,
2142 { messageId, &*_collageData },
2143 *_index,
2144 _groupThumbsAvailableWidth);
2145 } else if (_groupThumbs) {
2146 _groupThumbs->clear();
2147 _groupThumbs->resizeToWidth(_groupThumbsAvailableWidth);
2148 }
2149 if (_groupThumbs && !existed) {
2150 initGroupThumbs();
2151 }
2152 }
2153
initGroupThumbs()2154 void OverlayWidget::initGroupThumbs() {
2155 Expects(_groupThumbs != nullptr);
2156
2157 _groupThumbs->updateRequests(
2158 ) | rpl::start_with_next([this](QRect rect) {
2159 const auto shift = (width() / 2);
2160 _groupThumbsRect = QRect(
2161 shift + rect.x(),
2162 _groupThumbsTop,
2163 rect.width(),
2164 _groupThumbs->height());
2165 update(_groupThumbsRect);
2166 }, _groupThumbs->lifetime());
2167
2168 _groupThumbs->activateRequests(
2169 ) | rpl::start_with_next([this](View::GroupThumbs::Key key) {
2170 using CollageKey = View::GroupThumbs::CollageKey;
2171 if (const auto photoId = std::get_if<PhotoId>(&key)) {
2172 const auto photo = _session->data().photo(*photoId);
2173 moveToEntity({ photo, nullptr });
2174 } else if (const auto itemId = std::get_if<FullMsgId>(&key)) {
2175 moveToEntity(entityForItemId(*itemId));
2176 } else if (const auto collageKey = std::get_if<CollageKey>(&key)) {
2177 if (_collageData) {
2178 moveToEntity(entityForCollage(collageKey->index));
2179 }
2180 }
2181 }, _groupThumbs->lifetime());
2182
2183 _groupThumbsRect = QRect(
2184 _groupThumbsLeft,
2185 _groupThumbsTop,
2186 width() - 2 * _groupThumbsLeft,
2187 height() - _groupThumbsTop);
2188 }
2189
clearControlsState()2190 void OverlayWidget::clearControlsState() {
2191 _saveMsgStarted = 0;
2192 _loadRequest = 0;
2193 _over = _down = OverNone;
2194 _pressed = false;
2195 _dragging = 0;
2196 setCursor(style::cur_default);
2197 if (!_animations.empty()) {
2198 _animations.clear();
2199 _stateAnimation.stop();
2200 }
2201 if (!_animationOpacities.empty()) {
2202 _animationOpacities.clear();
2203 }
2204 }
2205
window() const2206 not_null<QWindow*> OverlayWidget::window() const {
2207 return _widget->windowHandle();
2208 }
2209
width() const2210 int OverlayWidget::width() const {
2211 return _widget->width();
2212 }
2213
height() const2214 int OverlayWidget::height() const {
2215 return _widget->height();
2216 }
2217
update()2218 void OverlayWidget::update() {
2219 _widget->update();
2220 }
2221
update(const QRegion & region)2222 void OverlayWidget::update(const QRegion ®ion) {
2223 _widget->update(region);
2224 }
2225
isHidden() const2226 bool OverlayWidget::isHidden() const {
2227 return _widget->isHidden();
2228 }
2229
widget() const2230 not_null<QWidget*> OverlayWidget::widget() const {
2231 return _widget;
2232 }
2233
hide()2234 void OverlayWidget::hide() {
2235 clearBeforeHide();
2236 applyHideWindowWorkaround();
2237 _widget->hide();
2238 }
2239
setCursor(style::cursor cursor)2240 void OverlayWidget::setCursor(style::cursor cursor) {
2241 _widget->setCursor(cursor);
2242 }
2243
setFocus()2244 void OverlayWidget::setFocus() {
2245 _widget->setFocus();
2246 }
2247
activate()2248 void OverlayWidget::activate() {
2249 _widget->raise();
2250 _widget->activateWindow();
2251 QApplication::setActiveWindow(_widget);
2252 setFocus();
2253 }
2254
show(OpenRequest request)2255 void OverlayWidget::show(OpenRequest request) {
2256 const auto document = request.document();
2257 const auto photo = request.photo();
2258 const auto contextItem = request.item();
2259 const auto contextPeer = request.peer();
2260 if (photo) {
2261 if (contextItem && contextPeer) {
2262 return;
2263 }
2264 setSession(&photo->session());
2265
2266 if (contextPeer) {
2267 setContext(contextPeer);
2268 } else if (contextItem) {
2269 setContext(contextItem);
2270 } else {
2271 setContext(v::null);
2272 }
2273
2274 clearControlsState();
2275 _firstOpenedPeerPhoto = (contextPeer != nullptr);
2276 assignMediaPointer(photo);
2277
2278 displayPhoto(photo);
2279 preloadData(0);
2280 activateControls();
2281 } else if (document) {
2282 setSession(&document->session());
2283
2284 if (contextItem) {
2285 setContext(contextItem);
2286 } else {
2287 setContext(v::null);
2288 }
2289
2290 clearControlsState();
2291
2292 _streamingStartPaused = false;
2293 displayDocument(
2294 document,
2295 request.cloudTheme()
2296 ? *request.cloudTheme()
2297 : Data::CloudTheme(),
2298 request.continueStreaming());
2299 if (!isHidden()) {
2300 preloadData(0);
2301 activateControls();
2302 }
2303 }
2304 if (const auto controller = request.controller()) {
2305 _window = base::make_weak(&controller->window());
2306 }
2307 }
2308
displayPhoto(not_null<PhotoData * > photo)2309 void OverlayWidget::displayPhoto(not_null<PhotoData*> photo) {
2310 if (photo->isNull()) {
2311 displayDocument(nullptr);
2312 return;
2313 }
2314 _touchbarDisplay.fire(TouchBarItemType::Photo);
2315
2316 clearStreaming();
2317 destroyThemePreview();
2318
2319 _fullScreenVideo = false;
2320 assignMediaPointer(photo);
2321 _rotation = _photo->owner().mediaRotation().get(_photo);
2322 _radial.stop();
2323
2324 refreshMediaViewer();
2325
2326 _staticContent = QImage();
2327 if (_photo->videoCanBePlayed()) {
2328 initStreaming();
2329 }
2330
2331 refreshCaption();
2332
2333 _blurred = true;
2334 _down = OverNone;
2335 if (!_staticContent.isNull()) {
2336 // Video thumbnail.
2337 const auto size = style::ConvertScale(
2338 flipSizeByRotation(_staticContent.size()));
2339 _w = size.width();
2340 _h = size.height();
2341 } else {
2342 const auto size = style::ConvertScale(flipSizeByRotation(QSize(
2343 photo->width(),
2344 photo->height())));
2345 _w = size.width();
2346 _h = size.height();
2347 }
2348 contentSizeChanged();
2349 refreshFromLabel();
2350 displayFinished();
2351 }
2352
destroyThemePreview()2353 void OverlayWidget::destroyThemePreview() {
2354 _themePreviewId = 0;
2355 _themePreviewShown = false;
2356 _themePreview.reset();
2357 _themeApply.destroy();
2358 _themeCancel.destroy();
2359 _themeShare.destroy();
2360 }
2361
redisplayContent()2362 void OverlayWidget::redisplayContent() {
2363 if (isHidden() || !_session) {
2364 return;
2365 } else if (_photo) {
2366 displayPhoto(_photo);
2367 } else {
2368 displayDocument(_document);
2369 }
2370 }
2371
2372 // Empty messages shown as docs: doc can be nullptr.
displayDocument(DocumentData * doc,const Data::CloudTheme & cloud,bool continueStreaming)2373 void OverlayWidget::displayDocument(
2374 DocumentData *doc,
2375 const Data::CloudTheme &cloud,
2376 bool continueStreaming) {
2377 _fullScreenVideo = false;
2378 _staticContent = QImage();
2379 clearStreaming(_document != doc);
2380 destroyThemePreview();
2381 assignMediaPointer(doc);
2382
2383 _rotation = _document
2384 ? _document->owner().mediaRotation().get(_document)
2385 : 0;
2386 _themeCloudData = cloud;
2387 _radial.stop();
2388
2389 _touchbarDisplay.fire(TouchBarItemType::None);
2390
2391 refreshMediaViewer();
2392 if (_document) {
2393 if (_document->sticker()) {
2394 if (const auto image = _documentMedia->getStickerLarge()) {
2395 setStaticContent(image->original());
2396 } else if (const auto thumbnail = _documentMedia->thumbnail()) {
2397 setStaticContent(thumbnail->pixBlurred(
2398 _document->dimensions.width(),
2399 _document->dimensions.height()
2400 ).toImage());
2401 }
2402 } else {
2403 if (_documentMedia->canBePlayed()
2404 && initStreaming(continueStreaming)) {
2405 } else if (_document->isVideoFile()) {
2406 _documentMedia->automaticLoad(fileOrigin(), _message);
2407 initStreamingThumbnail();
2408 } else if (_document->isTheme()) {
2409 _documentMedia->automaticLoad(fileOrigin(), _message);
2410 initThemePreview();
2411 } else {
2412 _documentMedia->automaticLoad(fileOrigin(), _message);
2413 _document->saveFromDataSilent();
2414 auto &location = _document->location(true);
2415 if (location.accessEnable()) {
2416 setStaticContent(PrepareStaticImage({
2417 .path = location.name(),
2418 }));
2419 if (!_staticContent.isNull()) {
2420 _touchbarDisplay.fire(TouchBarItemType::Photo);
2421 }
2422 } else {
2423 setStaticContent(PrepareStaticImage({
2424 .content = _documentMedia->bytes(),
2425 }));
2426 if (!_staticContent.isNull()) {
2427 _touchbarDisplay.fire(TouchBarItemType::Photo);
2428 }
2429 }
2430 location.accessDisable();
2431 }
2432 }
2433 }
2434 refreshCaption();
2435
2436 const auto docGeneric = Layout::DocumentGenericPreview::Create(_document);
2437 _docExt = docGeneric.ext;
2438 _docIconColor = docGeneric.color;
2439 _docIcon = docGeneric.icon();
2440
2441 int32 extmaxw = (st::mediaviewFileIconSize - st::mediaviewFileExtPadding * 2);
2442 _docExtWidth = st::mediaviewFileExtFont->width(_docExt);
2443 if (_docExtWidth > extmaxw) {
2444 _docExt = st::mediaviewFileExtFont->elided(_docExt, extmaxw, Qt::ElideMiddle);
2445 _docExtWidth = st::mediaviewFileExtFont->width(_docExt);
2446 }
2447 if (documentBubbleShown()) {
2448 if (_document && _document->hasThumbnail()) {
2449 _document->loadThumbnail(fileOrigin());
2450 const auto tw = _documentMedia->thumbnailSize().width();
2451 const auto th = _documentMedia->thumbnailSize().height();
2452 if (!tw || !th) {
2453 _docThumbx = _docThumby = _docThumbw = 0;
2454 } else if (tw > th) {
2455 _docThumbw = (tw * st::mediaviewFileIconSize) / th;
2456 _docThumbx = (_docThumbw - st::mediaviewFileIconSize) / 2;
2457 _docThumby = 0;
2458 } else {
2459 _docThumbw = st::mediaviewFileIconSize;
2460 _docThumbx = 0;
2461 _docThumby = ((th * _docThumbw) / tw - st::mediaviewFileIconSize) / 2;
2462 }
2463 }
2464
2465 int32 maxw = st::mediaviewFileSize.width() - st::mediaviewFileIconSize - st::mediaviewFilePadding * 3;
2466
2467 if (_document) {
2468 _docName = (_document->type == StickerDocument)
2469 ? tr::lng_in_dlg_sticker(tr::now)
2470 : (_document->type == AnimatedDocument
2471 ? qsl("GIF")
2472 : (_document->filename().isEmpty()
2473 ? tr::lng_mediaview_doc_image(tr::now)
2474 : _document->filename()));
2475 } else {
2476 _docName = tr::lng_message_empty(tr::now);
2477 }
2478 _docNameWidth = st::mediaviewFileNameFont->width(_docName);
2479 if (_docNameWidth > maxw) {
2480 _docName = st::mediaviewFileNameFont->elided(_docName, maxw, Qt::ElideMiddle);
2481 _docNameWidth = st::mediaviewFileNameFont->width(_docName);
2482 }
2483 } else if (_themePreviewShown) {
2484 updateThemePreviewGeometry();
2485 } else if (!_staticContent.isNull()) {
2486 const auto size = style::ConvertScale(
2487 flipSizeByRotation(_staticContent.size()));
2488 _w = size.width();
2489 _h = size.height();
2490 } else if (videoShown()) {
2491 const auto contentSize = style::ConvertScale(videoSize());
2492 _w = contentSize.width();
2493 _h = contentSize.height();
2494 }
2495 contentSizeChanged();
2496 if (videoShown()) {
2497 applyVideoSize();
2498 }
2499 refreshFromLabel();
2500 _blurred = false;
2501 if (_showAsPip && _streamed && !videoIsGifOrUserpic()) {
2502 switchToPip();
2503 } else {
2504 displayFinished();
2505 }
2506 }
2507
updateThemePreviewGeometry()2508 void OverlayWidget::updateThemePreviewGeometry() {
2509 if (_themePreviewShown) {
2510 auto previewRect = QRect((width() - st::themePreviewSize.width()) / 2, (height() - st::themePreviewSize.height()) / 2, st::themePreviewSize.width(), st::themePreviewSize.height());
2511 _themePreviewRect = previewRect.marginsAdded(st::themePreviewMargin);
2512 if (_themeApply) {
2513 auto right = qMax(width() - _themePreviewRect.x() - _themePreviewRect.width(), 0) + st::themePreviewMargin.right();
2514 auto bottom = qMin(height(), _themePreviewRect.y() + _themePreviewRect.height());
2515 _themeApply->moveToRight(right, bottom - st::themePreviewMargin.bottom() + (st::themePreviewMargin.bottom() - _themeApply->height()) / 2);
2516 right += _themeApply->width() + st::themePreviewButtonsSkip;
2517 _themeCancel->moveToRight(right, _themeApply->y());
2518 if (_themeShare) {
2519 _themeShare->moveToLeft(previewRect.x(), _themeApply->y());
2520 }
2521 }
2522
2523 // For context menu event.
2524 _x = _themePreviewRect.x();
2525 _y = _themePreviewRect.y();
2526 _w = _themePreviewRect.width();
2527 _h = _themePreviewRect.height();
2528 }
2529 }
2530
displayFinished()2531 void OverlayWidget::displayFinished() {
2532 updateControls();
2533 if (isHidden()) {
2534 moveToScreen();
2535 //setAttribute(Qt::WA_DontShowOnScreen);
2536 //OverlayParent::setVisibleHook(true);
2537 //OverlayParent::setVisibleHook(false);
2538 //setAttribute(Qt::WA_DontShowOnScreen, false);
2539 Ui::Platform::UpdateOverlayed(_widget);
2540 if (Platform::IsLinux()) {
2541 _widget->showFullScreen();
2542 } else {
2543 _widget->show();
2544 }
2545 Ui::Platform::ShowOverAll(_widget);
2546 activate();
2547 }
2548 }
2549
canInitStreaming() const2550 bool OverlayWidget::canInitStreaming() const {
2551 return (_document && _documentMedia->canBePlayed())
2552 || (_photo && _photo->videoCanBePlayed());
2553 }
2554
initStreaming(bool continueStreaming)2555 bool OverlayWidget::initStreaming(bool continueStreaming) {
2556 Expects(canInitStreaming());
2557
2558 if (_streamed) {
2559 return true;
2560 }
2561 initStreamingThumbnail();
2562 if (!createStreamingObjects()) {
2563 if (_document) {
2564 _document->setInappPlaybackFailed();
2565 } else {
2566 _photo->setVideoPlaybackFailed();
2567 }
2568 return false;
2569 }
2570
2571 Core::App().updateNonIdle();
2572
2573 _streamed->instance.player().updates(
2574 ) | rpl::start_with_next_error([=](Streaming::Update &&update) {
2575 handleStreamingUpdate(std::move(update));
2576 }, [=](Streaming::Error &&error) {
2577 handleStreamingError(std::move(error));
2578 }, _streamed->instance.lifetime());
2579
2580 if (continueStreaming) {
2581 _pip = nullptr;
2582 }
2583 if (!continueStreaming
2584 || (!_streamed->instance.player().active()
2585 && !_streamed->instance.player().finished())) {
2586 startStreamingPlayer();
2587 } else {
2588 updatePlaybackState();
2589 }
2590 return true;
2591 }
2592
startStreamingPlayer()2593 void OverlayWidget::startStreamingPlayer() {
2594 Expects(_streamed != nullptr);
2595
2596 const auto &player = _streamed->instance.player();
2597 if (player.playing()) {
2598 if (!_streamed->withSound) {
2599 return;
2600 }
2601 _pip = nullptr;
2602 } else if (!player.paused() && !player.finished() && !player.failed()) {
2603 _pip = nullptr;
2604 } else if (_pip && _streamed->withSound) {
2605 return;
2606 }
2607
2608 const auto position = _document
2609 ? _document->session().settings().mediaLastPlaybackPosition(
2610 _document->id)
2611 : _photo
2612 ? _photo->videoStartPosition()
2613 : 0;
2614 restartAtSeekPosition(position);
2615 }
2616
initStreamingThumbnail()2617 void OverlayWidget::initStreamingThumbnail() {
2618 Expects(_photo || _document);
2619
2620 _touchbarDisplay.fire(TouchBarItemType::Video);
2621
2622 const auto computePhotoThumbnail = [&] {
2623 const auto thumbnail = _photoMedia->image(Data::PhotoSize::Thumbnail);
2624 if (thumbnail) {
2625 return thumbnail;
2626 } else if (_peer && _peer->userpicPhotoId() == _photo->id) {
2627 if (const auto view = _peer->activeUserpicView()) {
2628 if (const auto image = view->image()) {
2629 return image;
2630 }
2631 }
2632 }
2633 return thumbnail;
2634 };
2635 const auto good = _document
2636 ? _documentMedia->goodThumbnail()
2637 : _photoMedia->image(Data::PhotoSize::Large);
2638 const auto thumbnail = _document
2639 ? _documentMedia->thumbnail()
2640 : computePhotoThumbnail();
2641 const auto blurred = _document
2642 ? _documentMedia->thumbnailInline()
2643 : _photoMedia->thumbnailInline();
2644 const auto size = _photo
2645 ? QSize(
2646 _photo->videoLocation().width(),
2647 _photo->videoLocation().height())
2648 : good
2649 ? good->size()
2650 : _document->dimensions;
2651 if (!good && !thumbnail && !blurred) {
2652 return;
2653 } else if (size.isEmpty()) {
2654 return;
2655 }
2656 const auto w = size.width();
2657 const auto h = size.height();
2658 const auto options = VideoThumbOptions(_document);
2659 const auto goodOptions = (options & ~Images::Option::Blurred);
2660 setStaticContent((good
2661 ? good
2662 : thumbnail
2663 ? thumbnail
2664 : blurred
2665 ? blurred
2666 : Image::BlankMedia().get())->pixNoCache(
2667 w,
2668 h,
2669 good ? goodOptions : options,
2670 w / cIntRetinaFactor(),
2671 h / cIntRetinaFactor()
2672 ).toImage());
2673 }
2674
streamingReady(Streaming::Information && info)2675 void OverlayWidget::streamingReady(Streaming::Information &&info) {
2676 if (videoShown()) {
2677 applyVideoSize();
2678 } else {
2679 updateContentRect();
2680 }
2681 }
2682
applyVideoSize()2683 void OverlayWidget::applyVideoSize() {
2684 const auto contentSize = style::ConvertScale(videoSize());
2685 if (contentSize != QSize(_width, _height)) {
2686 updateContentRect();
2687 _w = contentSize.width();
2688 _h = contentSize.height();
2689 contentSizeChanged();
2690 }
2691 updateContentRect();
2692 }
2693
createStreamingObjects()2694 bool OverlayWidget::createStreamingObjects() {
2695 Expects(_photo || _document);
2696
2697 if (_document) {
2698 _streamed = std::make_unique<Streamed>(
2699 _document,
2700 fileOrigin(),
2701 _widget,
2702 static_cast<PlaybackControls::Delegate*>(this),
2703 [=] { waitingAnimationCallback(); });
2704 } else {
2705 _streamed = std::make_unique<Streamed>(
2706 _photo,
2707 fileOrigin(),
2708 _widget,
2709 static_cast<PlaybackControls::Delegate*>(this),
2710 [=] { waitingAnimationCallback(); });
2711 }
2712 if (!_streamed->instance.valid()) {
2713 _streamed = nullptr;
2714 return false;
2715 }
2716 ++_streamedCreated;
2717 _streamed->instance.setPriority(kOverlayLoaderPriority);
2718 _streamed->instance.lockPlayer();
2719 _streamed->withSound = _document
2720 && (_document->isAudioFile()
2721 || _document->isVideoFile()
2722 || _document->isVoiceMessage()
2723 || _document->isVideoMessage());
2724
2725 if (videoIsGifOrUserpic()) {
2726 _streamed->controls.hide();
2727 } else {
2728 refreshClipControllerGeometry();
2729 _streamed->controls.show();
2730 }
2731 return true;
2732 }
2733
transformedShownContent() const2734 QImage OverlayWidget::transformedShownContent() const {
2735 return transformShownContent(
2736 videoShown() ? currentVideoFrameImage() : _staticContent,
2737 finalContentRotation());
2738 }
2739
transformShownContent(QImage content,int rotation) const2740 QImage OverlayWidget::transformShownContent(
2741 QImage content,
2742 int rotation) const {
2743 if (rotation) {
2744 content = RotateFrameImage(std::move(content), rotation);
2745 }
2746 if (videoShown()) {
2747 const auto requiredSize = videoSize();
2748 if (content.size() != requiredSize) {
2749 content = content.scaled(
2750 requiredSize,
2751 Qt::IgnoreAspectRatio,
2752 Qt::SmoothTransformation);
2753 }
2754 }
2755 return content;
2756 }
2757
handleStreamingUpdate(Streaming::Update && update)2758 void OverlayWidget::handleStreamingUpdate(Streaming::Update &&update) {
2759 using namespace Streaming;
2760
2761 v::match(update.data, [&](Information &update) {
2762 streamingReady(std::move(update));
2763 }, [&](const PreloadedVideo &update) {
2764 updatePlaybackState();
2765 }, [&](const UpdateVideo &update) {
2766 updateContentRect();
2767 Core::App().updateNonIdle();
2768 updatePlaybackState();
2769 }, [&](const PreloadedAudio &update) {
2770 updatePlaybackState();
2771 }, [&](const UpdateAudio &update) {
2772 updatePlaybackState();
2773 }, [&](WaitingForData) {
2774 }, [&](MutedByOther) {
2775 }, [&](Finished) {
2776 updatePlaybackState();
2777 });
2778 }
2779
handleStreamingError(Streaming::Error && error)2780 void OverlayWidget::handleStreamingError(Streaming::Error &&error) {
2781 Expects(_document || _photo);
2782
2783 if (error == Streaming::Error::NotStreamable) {
2784 if (_document) {
2785 _document->setNotSupportsStreaming();
2786 } else {
2787 _photo->setVideoPlaybackFailed();
2788 }
2789 } else if (error == Streaming::Error::OpenFailed) {
2790 if (_document) {
2791 _document->setInappPlaybackFailed();
2792 } else {
2793 _photo->setVideoPlaybackFailed();
2794 }
2795 }
2796 if (canInitStreaming()) {
2797 updatePlaybackState();
2798 } else {
2799 redisplayContent();
2800 }
2801 }
2802
initThemePreview()2803 void OverlayWidget::initThemePreview() {
2804 using namespace Window::Theme;
2805
2806 Assert(_document && _document->isTheme());
2807
2808 const auto bytes = _documentMedia->bytes();
2809 auto &location = _document->location();
2810 if (bytes.isEmpty()
2811 && (location.isEmpty() || !location.accessEnable())) {
2812 return;
2813 }
2814 _themePreviewShown = true;
2815
2816 auto current = CurrentData();
2817 current.backgroundId = Background()->id();
2818 current.backgroundImage = Background()->createCurrentImage();
2819 current.backgroundTiled = Background()->tile();
2820
2821 const auto &cloudList = _document->session().data().cloudThemes().list();
2822 const auto i = ranges::find(
2823 cloudList,
2824 _document->id,
2825 &Data::CloudTheme::documentId);
2826 const auto cloud = (i != end(cloudList)) ? *i : Data::CloudTheme();
2827 const auto isTrusted = (cloud.documentId != 0);
2828 const auto fields = [&] {
2829 auto result = _themeCloudData.id ? _themeCloudData : cloud;
2830 if (!result.documentId) {
2831 result.documentId = _document->id;
2832 }
2833 return result;
2834 }();
2835
2836 const auto weakSession = base::make_weak(&_document->session());
2837 const auto path = _document->location().name();
2838 const auto id = _themePreviewId = base::RandomValue<uint64>();
2839 const auto weak = Ui::MakeWeak(_widget);
2840 crl::async([=, data = std::move(current)]() mutable {
2841 auto preview = GeneratePreview(
2842 bytes,
2843 path,
2844 fields,
2845 std::move(data),
2846 Window::Theme::PreviewType::Extended);
2847 crl::on_main(weak, [=, result = std::move(preview)]() mutable {
2848 const auto session = weakSession.get();
2849 if (id != _themePreviewId || !session) {
2850 return;
2851 }
2852 _themePreviewId = 0;
2853 _themePreview = std::move(result);
2854 if (_themePreview) {
2855 _themeApply.create(
2856 _widget,
2857 tr::lng_theme_preview_apply(),
2858 st::themePreviewApplyButton);
2859 _themeApply->show();
2860 _themeApply->setClickedCallback([=] {
2861 const auto &object = Background()->themeObject();
2862 const auto currentlyIsCustom = !object.cloud.id
2863 && !IsEmbeddedTheme(object.pathAbsolute);
2864 auto preview = std::move(_themePreview);
2865 close();
2866 Apply(std::move(preview));
2867 if (isTrusted && !currentlyIsCustom) {
2868 KeepApplied();
2869 }
2870 });
2871 _themeCancel.create(
2872 _widget,
2873 tr::lng_cancel(),
2874 st::themePreviewCancelButton);
2875 _themeCancel->show();
2876 _themeCancel->setClickedCallback([this] { close(); });
2877 if (const auto slug = _themeCloudData.slug; !slug.isEmpty()) {
2878 _themeShare.create(
2879 _widget,
2880 tr::lng_theme_share(),
2881 st::themePreviewCancelButton);
2882 _themeShare->show();
2883 _themeShare->setClickedCallback([=] {
2884 QGuiApplication::clipboard()->setText(
2885 session->createInternalLinkFull("addtheme/" + slug));
2886 Ui::Toast::Show(
2887 _widget,
2888 tr::lng_background_link_copied(tr::now));
2889 });
2890 } else {
2891 _themeShare.destroy();
2892 }
2893 updateControls();
2894 }
2895 update();
2896 });
2897 });
2898 location.accessDisable();
2899 }
2900
refreshClipControllerGeometry()2901 void OverlayWidget::refreshClipControllerGeometry() {
2902 if (!_streamed || videoIsGifOrUserpic()) {
2903 return;
2904 }
2905
2906 if (_groupThumbs && _groupThumbs->hiding()) {
2907 _groupThumbs = nullptr;
2908 _groupThumbsRect = QRect();
2909 }
2910 const auto controllerBottom = _groupThumbs
2911 ? _groupThumbsTop
2912 : height();
2913 _streamed->controls.resize(st::mediaviewControllerSize);
2914 _streamed->controls.move(
2915 (width() - _streamed->controls.width()) / 2,
2916 controllerBottom - _streamed->controls.height() - st::mediaviewCaptionPadding.bottom() - st::mediaviewCaptionMargin.height());
2917 Ui::SendPendingMoveResizeEvents(&_streamed->controls);
2918 }
2919
playbackControlsPlay()2920 void OverlayWidget::playbackControlsPlay() {
2921 playbackPauseResume();
2922 }
2923
playbackControlsPause()2924 void OverlayWidget::playbackControlsPause() {
2925 playbackPauseResume();
2926 }
2927
playbackControlsToFullScreen()2928 void OverlayWidget::playbackControlsToFullScreen() {
2929 playbackToggleFullScreen();
2930 }
2931
playbackControlsFromFullScreen()2932 void OverlayWidget::playbackControlsFromFullScreen() {
2933 playbackToggleFullScreen();
2934 }
2935
playbackControlsToPictureInPicture()2936 void OverlayWidget::playbackControlsToPictureInPicture() {
2937 if (!videoIsGifOrUserpic()) {
2938 switchToPip();
2939 }
2940 }
2941
playbackControlsRotate()2942 void OverlayWidget::playbackControlsRotate() {
2943 _oldGeometry = contentGeometry();
2944 _geometryAnimation.stop();
2945 if (_photo) {
2946 auto &storage = _photo->owner().mediaRotation();
2947 storage.set(_photo, storage.get(_photo) - 90);
2948 _rotation = storage.get(_photo);
2949 redisplayContent();
2950 } else if (_document) {
2951 auto &storage = _document->owner().mediaRotation();
2952 storage.set(_document, storage.get(_document) - 90);
2953 _rotation = storage.get(_document);
2954 if (videoShown()) {
2955 applyVideoSize();
2956 } else {
2957 redisplayContent();
2958 }
2959 }
2960 if (_opengl) {
2961 _geometryAnimation.start(
2962 [=] { update(); },
2963 0.,
2964 1.,
2965 st::widgetFadeDuration/*,
2966 st::easeOutCirc*/);
2967 }
2968 }
2969
playbackPauseResume()2970 void OverlayWidget::playbackPauseResume() {
2971 Expects(_streamed != nullptr);
2972
2973 _streamed->resumeOnCallEnd = false;
2974 if (_streamed->instance.player().failed()) {
2975 clearStreaming();
2976 if (!canInitStreaming() || !initStreaming()) {
2977 redisplayContent();
2978 }
2979 } else if (_streamed->instance.player().finished()
2980 || !_streamed->instance.player().active()) {
2981 _streamingStartPaused = false;
2982 restartAtSeekPosition(0);
2983 } else if (_streamed->instance.player().paused()) {
2984 _streamed->instance.resume();
2985 updatePlaybackState();
2986 playbackPauseMusic();
2987 } else {
2988 _streamed->instance.pause();
2989 updatePlaybackState();
2990 }
2991 }
2992
seekRelativeTime(crl::time time)2993 void OverlayWidget::seekRelativeTime(crl::time time) {
2994 Expects(_streamed != nullptr);
2995
2996 const auto newTime = std::clamp(
2997 _streamed->instance.info().video.state.position + time,
2998 crl::time(0),
2999 _streamed->instance.info().video.state.duration);
3000 restartAtSeekPosition(newTime);
3001 }
3002
restartAtProgress(float64 progress)3003 void OverlayWidget::restartAtProgress(float64 progress) {
3004 Expects(_streamed != nullptr);
3005
3006 restartAtSeekPosition(_streamed->instance.info().video.state.duration
3007 * std::clamp(progress, 0., 1.));
3008 }
3009
restartAtSeekPosition(crl::time position)3010 void OverlayWidget::restartAtSeekPosition(crl::time position) {
3011 Expects(_streamed != nullptr);
3012
3013 if (videoShown()) {
3014 _streamed->instance.saveFrameToCover();
3015 const auto saved = base::take(_rotation);
3016 setStaticContent(transformedShownContent());
3017 _rotation = saved;
3018 updateContentRect();
3019 }
3020 auto options = Streaming::PlaybackOptions();
3021 options.position = position;
3022 if (!_streamed->withSound) {
3023 options.mode = Streaming::Mode::Video;
3024 options.loop = true;
3025 } else {
3026 Assert(_document != nullptr);
3027 const auto messageId = _message ? _message->fullId() : FullMsgId();
3028 options.audioId = AudioMsgId(_document, messageId);
3029 options.speed = Core::App().settings().videoPlaybackSpeed();
3030 if (_pip) {
3031 _pip = nullptr;
3032 }
3033 }
3034 _streamed->instance.play(options);
3035 if (_streamingStartPaused) {
3036 _streamed->instance.pause();
3037 } else {
3038 playbackPauseMusic();
3039 }
3040 _streamed->pausedBySeek = false;
3041
3042 updatePlaybackState();
3043 }
3044
playbackControlsSeekProgress(crl::time position)3045 void OverlayWidget::playbackControlsSeekProgress(crl::time position) {
3046 Expects(_streamed != nullptr);
3047
3048 if (!_streamed->instance.player().paused()
3049 && !_streamed->instance.player().finished()) {
3050 _streamed->pausedBySeek = true;
3051 playbackControlsPause();
3052 }
3053 }
3054
playbackControlsSeekFinished(crl::time position)3055 void OverlayWidget::playbackControlsSeekFinished(crl::time position) {
3056 Expects(_streamed != nullptr);
3057
3058 _streamingStartPaused = !_streamed->pausedBySeek
3059 && !_streamed->instance.player().finished();
3060 restartAtSeekPosition(position);
3061 }
3062
playbackControlsVolumeChanged(float64 volume)3063 void OverlayWidget::playbackControlsVolumeChanged(float64 volume) {
3064 if (_streamed) {
3065 Player::mixer()->setVideoVolume(volume);
3066 }
3067 Core::App().settings().setVideoVolume(volume);
3068 Core::App().saveSettingsDelayed();
3069 }
3070
playbackControlsCurrentVolume()3071 float64 OverlayWidget::playbackControlsCurrentVolume() {
3072 return Core::App().settings().videoVolume();
3073 }
3074
playbackControlsVolumeToggled()3075 void OverlayWidget::playbackControlsVolumeToggled() {
3076 const auto volume = Core::App().settings().videoVolume();
3077 playbackControlsVolumeChanged(volume ? 0. : _lastPositiveVolume);
3078 }
3079
playbackControlsVolumeChangeFinished()3080 void OverlayWidget::playbackControlsVolumeChangeFinished() {
3081 const auto volume = Core::App().settings().videoVolume();
3082 if (volume > 0.) {
3083 _lastPositiveVolume = volume;
3084 }
3085 }
3086
playbackControlsSpeedChanged(float64 speed)3087 void OverlayWidget::playbackControlsSpeedChanged(float64 speed) {
3088 DEBUG_LOG(("Media playback speed: change to %1.").arg(speed));
3089 if (_document) {
3090 DEBUG_LOG(("Media playback speed: %1 to settings.").arg(speed));
3091 Core::App().settings().setVideoPlaybackSpeed(speed);
3092 Core::App().saveSettingsDelayed();
3093 }
3094 if (_streamed && !videoIsGifOrUserpic()) {
3095 DEBUG_LOG(("Media playback speed: %1 to _streamed.").arg(speed));
3096 _streamed->instance.setSpeed(speed);
3097 }
3098 }
3099
playbackControlsCurrentSpeed()3100 float64 OverlayWidget::playbackControlsCurrentSpeed() {
3101 const auto result = Core::App().settings().videoPlaybackSpeed();
3102 DEBUG_LOG(("Media playback speed: now %1.").arg(result));
3103 return result;
3104 }
3105
switchToPip()3106 void OverlayWidget::switchToPip() {
3107 Expects(_streamed != nullptr);
3108 Expects(_document != nullptr);
3109
3110 const auto document = _document;
3111 const auto message = _message;
3112 const auto closeAndContinue = [=] {
3113 _showAsPip = false;
3114 show(OpenRequest(
3115 findWindow(false),
3116 document,
3117 message,
3118 true));
3119 };
3120 _showAsPip = true;
3121 _pip = std::make_unique<PipWrap>(
3122 _widget,
3123 document,
3124 message ? message->fullId() : FullMsgId(),
3125 _streamed->instance.shared(),
3126 closeAndContinue,
3127 [=] { _pip = nullptr; });
3128 if (isHidden()) {
3129 clearBeforeHide();
3130 clearAfterHide();
3131 } else {
3132 close();
3133 if (const auto window = Core::App().activeWindow()) {
3134 window->activate();
3135 }
3136 }
3137 }
3138
playbackToggleFullScreen()3139 void OverlayWidget::playbackToggleFullScreen() {
3140 Expects(_streamed != nullptr);
3141
3142 if (!videoShown() || (videoIsGifOrUserpic() && !_fullScreenVideo)) {
3143 return;
3144 }
3145 _fullScreenVideo = !_fullScreenVideo;
3146 if (_fullScreenVideo) {
3147 _fullScreenZoomCache = _zoom;
3148 setZoomLevel(kZoomToScreenLevel, true);
3149 } else {
3150 setZoomLevel(_fullScreenZoomCache, true);
3151 _streamed->controls.showAnimated();
3152 }
3153
3154 _streamed->controls.setInFullScreen(_fullScreenVideo);
3155 _touchbarFullscreenToggled.fire_copy(_fullScreenVideo);
3156 updateControls();
3157 update();
3158 }
3159
playbackPauseOnCall()3160 void OverlayWidget::playbackPauseOnCall() {
3161 Expects(_streamed != nullptr);
3162
3163 if (_streamed->instance.player().finished()
3164 || _streamed->instance.player().paused()) {
3165 return;
3166 }
3167 _streamed->resumeOnCallEnd = true;
3168 _streamed->instance.pause();
3169 updatePlaybackState();
3170 }
3171
playbackResumeOnCall()3172 void OverlayWidget::playbackResumeOnCall() {
3173 Expects(_streamed != nullptr);
3174
3175 if (_streamed->resumeOnCallEnd) {
3176 _streamed->resumeOnCallEnd = false;
3177 _streamed->instance.resume();
3178 updatePlaybackState();
3179 playbackPauseMusic();
3180 }
3181 }
3182
playbackPauseMusic()3183 void OverlayWidget::playbackPauseMusic() {
3184 Expects(_streamed != nullptr);
3185
3186 if (!_streamed->withSound) {
3187 return;
3188 }
3189 Player::instance()->pause(AudioMsgId::Type::Voice);
3190 Player::instance()->pause(AudioMsgId::Type::Song);
3191 }
3192
updatePlaybackState()3193 void OverlayWidget::updatePlaybackState() {
3194 Expects(_streamed != nullptr);
3195
3196 if (videoIsGifOrUserpic()) {
3197 return;
3198 }
3199 const auto state = _streamed->instance.player().prepareLegacyState();
3200 if (state.position != kTimeUnknown && state.length != kTimeUnknown) {
3201 _streamed->controls.updatePlayback(state);
3202 _touchbarTrackState.fire_copy(state);
3203 }
3204 }
3205
validatePhotoImage(Image * image,bool blurred)3206 void OverlayWidget::validatePhotoImage(Image *image, bool blurred) {
3207 if (!image) {
3208 return;
3209 } else if (!_staticContent.isNull() && (blurred || !_blurred)) {
3210 return;
3211 }
3212 const auto use = flipSizeByRotation({ _width, _height })
3213 * cIntRetinaFactor();
3214 setStaticContent(image->pixNoCache(
3215 use.width(),
3216 use.height(),
3217 Images::Option::Smooth
3218 | (blurred ? Images::Option::Blurred : Images::Option(0))
3219 ).toImage());
3220 _blurred = blurred;
3221 }
3222
validatePhotoCurrentImage()3223 void OverlayWidget::validatePhotoCurrentImage() {
3224 if (!_photo) {
3225 return;
3226 }
3227 validatePhotoImage(_photoMedia->image(Data::PhotoSize::Large), false);
3228 validatePhotoImage(_photoMedia->image(Data::PhotoSize::Thumbnail), true);
3229 validatePhotoImage(_photoMedia->image(Data::PhotoSize::Small), true);
3230 validatePhotoImage(_photoMedia->thumbnailInline(), true);
3231 if (_staticContent.isNull()
3232 && !_message
3233 && _peer
3234 && _peer->hasUserpic()) {
3235 if (const auto view = _peer->activeUserpicView()) {
3236 validatePhotoImage(view->image(), true);
3237 }
3238 }
3239 if (_staticContent.isNull()) {
3240 _photoMedia->wanted(Data::PhotoSize::Small, fileOrigin());
3241 }
3242 }
3243
chooseRenderer(Ui::GL::Capabilities capabilities)3244 Ui::GL::ChosenRenderer OverlayWidget::chooseRenderer(
3245 Ui::GL::Capabilities capabilities) {
3246 const auto use = Platform::IsMac()
3247 ? true
3248 : capabilities.transparency;
3249 LOG(("OpenGL: %1 (OverlayWidget)").arg(Logs::b(use)));
3250 if (use) {
3251 _opengl = true;
3252 return {
3253 .renderer = std::make_unique<RendererGL>(this),
3254 .backend = Ui::GL::Backend::OpenGL,
3255 };
3256 }
3257 return {
3258 .renderer = std::make_unique<RendererSW>(this),
3259 .backend = Ui::GL::Backend::Raster,
3260 };
3261 }
3262
paint(not_null<Renderer * > renderer)3263 void OverlayWidget::paint(not_null<Renderer*> renderer) {
3264 renderer->paintBackground();
3265 if (contentShown()) {
3266 if (videoShown()) {
3267 renderer->paintTransformedVideoFrame(contentGeometry());
3268 if (_streamed->instance.player().ready()) {
3269 _streamed->instance.markFrameShown();
3270 }
3271 } else {
3272 validatePhotoCurrentImage();
3273 const auto fillTransparentBackground = (!_document
3274 || (!_document->sticker() && !_document->isVideoMessage()))
3275 && _staticContentTransparent;
3276 renderer->paintTransformedStaticContent(
3277 _staticContent,
3278 contentGeometry(),
3279 _staticContentTransparent,
3280 fillTransparentBackground);
3281 }
3282 paintRadialLoading(renderer);
3283 } else {
3284 if (_themePreviewShown) {
3285 renderer->paintThemePreview(_themePreviewRect);
3286 } else if (documentBubbleShown() && !_docRect.isEmpty()) {
3287 renderer->paintDocumentBubble(_docRect, _docIconRect);
3288 }
3289 }
3290 updateSaveMsgState();
3291 if (_saveMsgStarted && _saveMsgOpacity.current() > 0.) {
3292 renderer->paintSaveMsg(_saveMsg);
3293 }
3294
3295 const auto opacity = _fullScreenVideo ? 0. : _controlsOpacity.current();
3296 if (opacity > 0) {
3297 paintControls(renderer, opacity);
3298 renderer->paintFooter(footerGeometry(), opacity);
3299 if (!_caption.isEmpty()) {
3300 renderer->paintCaption(captionGeometry(), opacity);
3301 }
3302 if (_groupThumbs) {
3303 renderer->paintGroupThumbs(
3304 QRect(
3305 _groupThumbsLeft,
3306 _groupThumbsTop,
3307 width() - 2 * _groupThumbsLeft,
3308 _groupThumbs->height()),
3309 opacity);
3310 }
3311 }
3312 checkGroupThumbsAnimation();
3313 }
3314
checkGroupThumbsAnimation()3315 void OverlayWidget::checkGroupThumbsAnimation() {
3316 if (_groupThumbs
3317 && (!_streamed || _streamed->instance.player().ready())) {
3318 _groupThumbs->checkForAnimationStart();
3319 }
3320 }
3321
paintRadialLoading(not_null<Renderer * > renderer)3322 void OverlayWidget::paintRadialLoading(not_null<Renderer*> renderer) {
3323 const auto radial = _radial.animating();
3324 if (_streamed) {
3325 if (!_streamed->instance.waitingShown()) {
3326 return;
3327 }
3328 } else if (!radial && (!_document || _documentMedia->loaded())) {
3329 return;
3330 }
3331
3332 const auto radialOpacity = radial ? _radial.opacity() : 0.;
3333 const auto inner = radialRect();
3334 Assert(!inner.isEmpty());
3335
3336 renderer->paintRadialLoading(inner, radial, radialOpacity);
3337 }
3338
paintRadialLoadingContent(Painter & p,QRect inner,bool radial,float64 radialOpacity) const3339 void OverlayWidget::paintRadialLoadingContent(
3340 Painter &p,
3341 QRect inner,
3342 bool radial,
3343 float64 radialOpacity) const {
3344 const auto arc = inner.marginsRemoved(QMargins(
3345 st::radialLine,
3346 st::radialLine,
3347 st::radialLine,
3348 st::radialLine));
3349 const auto paintBg = [&](float64 opacity, QBrush brush) {
3350 p.setOpacity(opacity);
3351 p.setPen(Qt::NoPen);
3352 p.setBrush(brush);
3353 {
3354 PainterHighQualityEnabler hq(p);
3355 p.drawEllipse(inner);
3356 }
3357 p.setOpacity(1.);
3358 };
3359
3360 if (_streamed) {
3361 paintBg(
3362 _streamed->instance.waitingOpacity(),
3363 st::radialBg);
3364 Ui::InfiniteRadialAnimation::Draw(
3365 p,
3366 _streamed->instance.waitingState(),
3367 arc.topLeft(),
3368 arc.size(),
3369 width(),
3370 st::radialFg,
3371 st::radialLine);
3372 return;
3373 }
3374 if (_photo) {
3375 paintBg(radialOpacity, st::radialBg);
3376 } else {
3377 const auto o = overLevel(OverIcon);
3378 paintBg(
3379 _documentMedia->loaded() ? radialOpacity : 1.,
3380 anim::brush(st::msgDateImgBg, st::msgDateImgBgOver, o));
3381
3382 const auto icon = [&]() -> const style::icon * {
3383 if (radial || _document->loading()) {
3384 return &st::historyFileThumbCancel;
3385 }
3386 return &st::historyFileThumbDownload;
3387 }();
3388 if (icon) {
3389 icon->paintInCenter(p, inner);
3390 }
3391 }
3392 if (radial) {
3393 p.setOpacity(1);
3394 _radial.draw(p, arc, st::radialLine, st::radialFg);
3395 }
3396 }
3397
paintThemePreviewContent(Painter & p,QRect outer,QRect clip)3398 void OverlayWidget::paintThemePreviewContent(
3399 Painter &p,
3400 QRect outer,
3401 QRect clip) {
3402 const auto fill = outer.intersected(clip);
3403 if (!fill.isEmpty()) {
3404 if (_themePreview) {
3405 p.drawImage(
3406 outer.topLeft(),
3407 _themePreview->preview);
3408 } else {
3409 p.fillRect(fill, st::themePreviewBg);
3410 p.setFont(st::themePreviewLoadingFont);
3411 p.setPen(st::themePreviewLoadingFg);
3412 p.drawText(
3413 outer,
3414 (_themePreviewId
3415 ? tr::lng_theme_preview_generating(tr::now)
3416 : tr::lng_theme_preview_invalid(tr::now)),
3417 QTextOption(style::al_center));
3418 }
3419 }
3420
3421 const auto fillOverlay = [&](QRect fill) {
3422 const auto clipped = fill.intersected(clip);
3423 if (!clipped.isEmpty()) {
3424 p.setOpacity(st::themePreviewOverlayOpacity);
3425 p.fillRect(clipped, st::themePreviewBg);
3426 p.setOpacity(1.);
3427 }
3428 };
3429 auto titleRect = QRect(
3430 outer.x(),
3431 outer.y(),
3432 outer.width(),
3433 st::themePreviewMargin.top());
3434 if (titleRect.x() < 0) {
3435 titleRect = QRect(
3436 0,
3437 outer.y(),
3438 width(),
3439 st::themePreviewMargin.top());
3440 }
3441 if (titleRect.y() < 0) {
3442 titleRect.moveTop(0);
3443 fillOverlay(titleRect);
3444 }
3445 titleRect = titleRect.marginsRemoved(QMargins(
3446 st::themePreviewMargin.left(),
3447 st::themePreviewTitleTop,
3448 st::themePreviewMargin.right(),
3449 (titleRect.height()
3450 - st::themePreviewTitleTop
3451 - st::themePreviewTitleFont->height)));
3452 if (titleRect.intersects(clip)) {
3453 p.setFont(st::themePreviewTitleFont);
3454 p.setPen(st::themePreviewTitleFg);
3455 const auto title = _themeCloudData.title.isEmpty()
3456 ? tr::lng_theme_preview_title(tr::now)
3457 : _themeCloudData.title;
3458 const auto elided = st::themePreviewTitleFont->elided(
3459 title,
3460 titleRect.width());
3461 p.drawTextLeft(titleRect.x(), titleRect.y(), width(), elided);
3462 }
3463
3464 auto buttonsRect = QRect(
3465 outer.x(),
3466 outer.y() + outer.height() - st::themePreviewMargin.bottom(),
3467 outer.width(),
3468 st::themePreviewMargin.bottom());
3469 if (buttonsRect.y() + buttonsRect.height() > height()) {
3470 buttonsRect.moveTop(height() - buttonsRect.height());
3471 fillOverlay(buttonsRect);
3472 }
3473 if (_themeShare && _themeCloudData.usersCount > 0) {
3474 p.setFont(st::boxTextFont);
3475 p.setPen(st::windowSubTextFg);
3476 const auto left = outer.x()
3477 + (_themeShare->x() - _themePreviewRect.x())
3478 + _themeShare->width()
3479 - (st::themePreviewCancelButton.width / 2);
3480 const auto baseline = outer.y()
3481 + (_themeShare->y() - _themePreviewRect.y())
3482 + st::themePreviewCancelButton.padding.top()
3483 + st::themePreviewCancelButton.textTop
3484 + st::themePreviewCancelButton.font->ascent;
3485 p.drawText(
3486 left,
3487 baseline,
3488 tr::lng_theme_preview_users(
3489 tr::now,
3490 lt_count,
3491 _themeCloudData.usersCount));
3492 }
3493 }
3494
paintDocumentBubbleContent(Painter & p,QRect outer,QRect icon,QRect clip) const3495 void OverlayWidget::paintDocumentBubbleContent(
3496 Painter &p,
3497 QRect outer,
3498 QRect icon,
3499 QRect clip) const {
3500 p.fillRect(outer, st::mediaviewFileBg);
3501 if (icon.intersects(clip)) {
3502 if (!_document || !_document->hasThumbnail()) {
3503 p.fillRect(icon, _docIconColor);
3504 const auto radial = _radial.animating();
3505 const auto radialOpacity = radial ? _radial.opacity() : 0.;
3506 if ((!_document || _documentMedia->loaded()) && (!radial || radialOpacity < 1) && _docIcon) {
3507 _docIcon->paint(p, icon.x() + (icon.width() - _docIcon->width()), icon.y(), width());
3508 p.setPen(st::mediaviewFileExtFg);
3509 p.setFont(st::mediaviewFileExtFont);
3510 if (!_docExt.isEmpty()) {
3511 p.drawText(icon.x() + (icon.width() - _docExtWidth) / 2, icon.y() + st::mediaviewFileExtTop + st::mediaviewFileExtFont->ascent, _docExt);
3512 }
3513 }
3514 } else if (const auto thumbnail = _documentMedia->thumbnail()) {
3515 int32 rf(cIntRetinaFactor());
3516 p.drawPixmap(icon.topLeft(), thumbnail->pix(_docThumbw), QRect(_docThumbx * rf, _docThumby * rf, st::mediaviewFileIconSize * rf, st::mediaviewFileIconSize * rf));
3517 }
3518 }
3519 if (!icon.contains(clip)) {
3520 p.setPen(st::mediaviewFileNameFg);
3521 p.setFont(st::mediaviewFileNameFont);
3522 p.drawTextLeft(outer.x() + 2 * st::mediaviewFilePadding + st::mediaviewFileIconSize, outer.y() + st::mediaviewFilePadding + st::mediaviewFileNameTop, width(), _docName, _docNameWidth);
3523
3524 p.setPen(st::mediaviewFileSizeFg);
3525 p.setFont(st::mediaviewFont);
3526 p.drawTextLeft(outer.x() + 2 * st::mediaviewFilePadding + st::mediaviewFileIconSize, outer.y() + st::mediaviewFilePadding + st::mediaviewFileSizeTop, width(), _docSize, _docSizeWidth);
3527 }
3528 }
3529
paintSaveMsgContent(Painter & p,QRect outer,QRect clip)3530 void OverlayWidget::paintSaveMsgContent(
3531 Painter &p,
3532 QRect outer,
3533 QRect clip) {
3534 p.setOpacity(_saveMsgOpacity.current());
3535 Ui::FillRoundRect(p, outer, st::mediaviewSaveMsgBg, Ui::MediaviewSaveCorners);
3536 st::mediaviewSaveMsgCheck.paint(p, outer.topLeft() + st::mediaviewSaveMsgCheckPos, width());
3537
3538 p.setPen(st::mediaviewSaveMsgFg);
3539 p.setTextPalette(st::mediaviewTextPalette);
3540 _saveMsgText.draw(p, outer.x() + st::mediaviewSaveMsgPadding.left(), outer.y() + st::mediaviewSaveMsgPadding.top(), outer.width() - st::mediaviewSaveMsgPadding.left() - st::mediaviewSaveMsgPadding.right());
3541 p.restoreTextPalette();
3542 p.setOpacity(1);
3543 }
3544
paintControls(not_null<Renderer * > renderer,float64 opacity)3545 void OverlayWidget::paintControls(
3546 not_null<Renderer*> renderer,
3547 float64 opacity) {
3548 struct Control {
3549 OverState state = OverNone;
3550 bool visible = false;
3551 const QRect &outer;
3552 const QRect &inner;
3553 const style::icon &icon;
3554 };
3555 const QRect kEmpty;
3556 // When adding / removing controls please update RendererGL.
3557 const Control controls[] = {
3558 {
3559 OverLeftNav,
3560 _leftNavVisible,
3561 _leftNav,
3562 _leftNavIcon,
3563 st::mediaviewLeft },
3564 {
3565 OverRightNav,
3566 _rightNavVisible,
3567 _rightNav,
3568 _rightNavIcon,
3569 st::mediaviewRight },
3570 {
3571 OverClose,
3572 true,
3573 _closeNav,
3574 _closeNavIcon,
3575 st::mediaviewClose },
3576 {
3577 OverSave,
3578 _saveVisible,
3579 kEmpty,
3580 _saveNavIcon,
3581 st::mediaviewSave },
3582 {
3583 OverRotate,
3584 _rotateVisible,
3585 kEmpty,
3586 _rotateNavIcon,
3587 st::mediaviewRotate },
3588 {
3589 OverMore,
3590 true,
3591 kEmpty,
3592 _moreNavIcon,
3593 st::mediaviewMore },
3594 };
3595
3596 renderer->paintControlsStart();
3597 for (const auto &control : controls) {
3598 if (!control.visible) {
3599 continue;
3600 }
3601 const auto bg = overLevel(control.state);
3602 const auto icon = bg * st::mediaviewIconOverOpacity
3603 + (1 - bg) * st::mediaviewIconOpacity;
3604 renderer->paintControl(
3605 control.state,
3606 control.outer,
3607 bg * opacity,
3608 control.inner,
3609 icon * opacity,
3610 control.icon);
3611 }
3612 }
3613
paintFooterContent(Painter & p,QRect outer,QRect clip,float64 opacity)3614 void OverlayWidget::paintFooterContent(
3615 Painter &p,
3616 QRect outer,
3617 QRect clip,
3618 float64 opacity) {
3619 p.setPen(st::mediaviewControlFg);
3620 p.setFont(st::mediaviewThickFont);
3621
3622 // header
3623 const auto shift = outer.topLeft() - _headerNav.topLeft();
3624 const auto header = _headerNav.translated(shift);
3625 const auto name = _nameNav.translated(shift);
3626 const auto date = _dateNav.translated(shift);
3627 if (header.intersects(clip)) {
3628 auto o = _headerHasLink ? overLevel(OverHeader) : 0;
3629 p.setOpacity((o * st::mediaviewIconOverOpacity + (1 - o) * st::mediaviewIconOpacity) * opacity);
3630 p.drawText(header.left(), header.top() + st::mediaviewThickFont->ascent, _headerText);
3631
3632 if (o > 0) {
3633 p.setOpacity(o * opacity);
3634 p.drawLine(header.left(), header.top() + st::mediaviewThickFont->ascent + 1, header.right(), header.top() + st::mediaviewThickFont->ascent + 1);
3635 }
3636 }
3637
3638 p.setFont(st::mediaviewFont);
3639
3640 // name
3641 if (_nameNav.isValid() && name.intersects(clip)) {
3642 float64 o = _from ? overLevel(OverName) : 0.;
3643 p.setOpacity((o * st::mediaviewIconOverOpacity + (1 - o) * st::mediaviewIconOpacity) * opacity);
3644 _fromNameLabel.drawElided(p, name.left(), name.top(), name.width());
3645
3646 if (o > 0) {
3647 p.setOpacity(o * opacity);
3648 p.drawLine(name.left(), name.top() + st::mediaviewFont->ascent + 1, name.right(), name.top() + st::mediaviewFont->ascent + 1);
3649 }
3650 }
3651
3652 // date
3653 if (date.intersects(clip)) {
3654 float64 o = overLevel(OverDate);
3655 p.setOpacity((o * st::mediaviewIconOverOpacity + (1 - o) * st::mediaviewIconOpacity) * opacity);
3656 p.drawText(date.left(), date.top() + st::mediaviewFont->ascent, _dateText);
3657
3658 if (o > 0) {
3659 p.setOpacity(o * opacity);
3660 p.drawLine(date.left(), date.top() + st::mediaviewFont->ascent + 1, date.right(), date.top() + st::mediaviewFont->ascent + 1);
3661 }
3662 }
3663 }
3664
footerGeometry() const3665 QRect OverlayWidget::footerGeometry() const {
3666 return _headerNav.united(_nameNav).united(_dateNav);
3667 }
3668
paintCaptionContent(Painter & p,QRect outer,QRect clip,float64 opacity)3669 void OverlayWidget::paintCaptionContent(
3670 Painter &p,
3671 QRect outer,
3672 QRect clip,
3673 float64 opacity) {
3674 const auto inner = outer.marginsRemoved(st::mediaviewCaptionPadding);
3675 p.setOpacity(opacity);
3676 p.setBrush(st::mediaviewCaptionBg);
3677 p.setPen(Qt::NoPen);
3678 p.drawRoundedRect(outer, st::mediaviewCaptionRadius, st::mediaviewCaptionRadius);
3679 if (inner.intersects(clip)) {
3680 p.setTextPalette(st::mediaviewTextPalette);
3681 p.setPen(st::mediaviewCaptionFg);
3682 _caption.drawElided(p, inner.x(), inner.y(), inner.width(), inner.height() / st::mediaviewCaptionStyle.font->height);
3683 p.restoreTextPalette();
3684 }
3685 }
3686
captionGeometry() const3687 QRect OverlayWidget::captionGeometry() const {
3688 return _captionRect.marginsAdded(st::mediaviewCaptionPadding);
3689 }
3690
paintGroupThumbsContent(Painter & p,QRect outer,QRect clip,float64 opacity)3691 void OverlayWidget::paintGroupThumbsContent(
3692 Painter &p,
3693 QRect outer,
3694 QRect clip,
3695 float64 opacity) {
3696 p.setOpacity(opacity);
3697 _groupThumbs->paint(p, outer.x(), outer.y(), width());
3698 if (_groupThumbs->hidden()) {
3699 _groupThumbs = nullptr;
3700 _groupThumbsRect = QRect();
3701 }
3702 }
3703
updateSaveMsgState()3704 void OverlayWidget::updateSaveMsgState() {
3705 if (!_saveMsgStarted) {
3706 return;
3707 }
3708 float64 dt = float64(crl::now()) - _saveMsgStarted;
3709 float64 hidingDt = dt - st::mediaviewSaveMsgShowing - st::mediaviewSaveMsgShown;
3710 if (dt >= st::mediaviewSaveMsgShowing
3711 + st::mediaviewSaveMsgShown
3712 + st::mediaviewSaveMsgHiding) {
3713 _saveMsgStarted = 0;
3714 return;
3715 }
3716 if (hidingDt >= 0 && _saveMsgOpacity.to() > 0.5) {
3717 _saveMsgOpacity.start(0);
3718 }
3719 float64 progress = (hidingDt >= 0) ? (hidingDt / st::mediaviewSaveMsgHiding) : (dt / st::mediaviewSaveMsgShowing);
3720 _saveMsgOpacity.update(qMin(progress, 1.), anim::linear);
3721 if (!_blurred) {
3722 const auto nextFrame = (dt < st::mediaviewSaveMsgShowing || hidingDt >= 0)
3723 ? int(AnimationTimerDelta)
3724 : (st::mediaviewSaveMsgShowing + st::mediaviewSaveMsgShown + 1 - dt);
3725 _saveMsgUpdater.callOnce(nextFrame);
3726 }
3727 }
3728
handleKeyPress(not_null<QKeyEvent * > e)3729 void OverlayWidget::handleKeyPress(not_null<QKeyEvent*> e) {
3730 const auto key = e->key();
3731 const auto modifiers = e->modifiers();
3732 const auto ctrl = modifiers.testFlag(Qt::ControlModifier);
3733 if (_streamed) {
3734 // Ctrl + F for full screen toggle is in eventFilter().
3735 const auto toggleFull = (modifiers.testFlag(Qt::AltModifier) || ctrl)
3736 && (key == Qt::Key_Enter || key == Qt::Key_Return);
3737 if (toggleFull) {
3738 playbackToggleFullScreen();
3739 return;
3740 } else if (key == Qt::Key_Space) {
3741 playbackPauseResume();
3742 return;
3743 } else if (_fullScreenVideo) {
3744 if (key == Qt::Key_Escape) {
3745 playbackToggleFullScreen();
3746 } else if (key == Qt::Key_0) {
3747 activateControls();
3748 restartAtSeekPosition(0);
3749 } else if (key >= Qt::Key_1 && key <= Qt::Key_9) {
3750 activateControls();
3751 const auto index = int(key - Qt::Key_0);
3752 restartAtProgress(index / 10.0);
3753 } else if (key == Qt::Key_Left) {
3754 activateControls();
3755 seekRelativeTime(-kSeekTimeMs);
3756 } else if (key == Qt::Key_Right) {
3757 activateControls();
3758 seekRelativeTime(kSeekTimeMs);
3759 }
3760
3761 return;
3762 }
3763 }
3764 if (!_menu && key == Qt::Key_Escape) {
3765 if (_document && _document->loading() && !_streamed) {
3766 handleDocumentClick();
3767 } else {
3768 close();
3769 }
3770 } else if (e == QKeySequence::Save || e == QKeySequence::SaveAs) {
3771 saveAs();
3772 } else if (key == Qt::Key_Copy || (key == Qt::Key_C && ctrl)) {
3773 copyMedia();
3774 } else if (key == Qt::Key_Enter
3775 || key == Qt::Key_Return
3776 || key == Qt::Key_Space) {
3777 if (_streamed) {
3778 playbackPauseResume();
3779 } else if (_document
3780 && !_document->loading()
3781 && (documentBubbleShown() || !_documentMedia->loaded())) {
3782 handleDocumentClick();
3783 }
3784 } else if (key == Qt::Key_Left) {
3785 if (_controlsHideTimer.isActive()) {
3786 activateControls();
3787 }
3788 moveToNext(-1);
3789 } else if (key == Qt::Key_Right) {
3790 if (_controlsHideTimer.isActive()) {
3791 activateControls();
3792 }
3793 moveToNext(1);
3794 } else if (ctrl) {
3795 if (key == Qt::Key_Plus
3796 || key == Qt::Key_Equal
3797 || key == Qt::Key_Asterisk
3798 || key == ']') {
3799 zoomIn();
3800 } else if (key == Qt::Key_Minus || key == Qt::Key_Underscore) {
3801 zoomOut();
3802 } else if (key == Qt::Key_0) {
3803 zoomReset();
3804 } else if (key == Qt::Key_I) {
3805 update();
3806 }
3807 }
3808 }
3809
handleWheelEvent(not_null<QWheelEvent * > e)3810 void OverlayWidget::handleWheelEvent(not_null<QWheelEvent*> e) {
3811 constexpr auto step = int(QWheelEvent::DefaultDeltasPerStep);
3812
3813 _verticalWheelDelta += e->angleDelta().y();
3814 while (qAbs(_verticalWheelDelta) >= step) {
3815 if (_verticalWheelDelta < 0) {
3816 _verticalWheelDelta += step;
3817 if (e->modifiers().testFlag(Qt::ControlModifier)) {
3818 zoomOut();
3819 } else if (e->source() == Qt::MouseEventNotSynthesized) {
3820 moveToNext(1);
3821 }
3822 } else {
3823 _verticalWheelDelta -= step;
3824 if (e->modifiers().testFlag(Qt::ControlModifier)) {
3825 zoomIn();
3826 } else if (e->source() == Qt::MouseEventNotSynthesized) {
3827 moveToNext(-1);
3828 }
3829 }
3830 }
3831 }
3832
setZoomLevel(int newZoom,bool force)3833 void OverlayWidget::setZoomLevel(int newZoom, bool force) {
3834 if (!force && _zoom == newZoom) {
3835 return;
3836 }
3837
3838 const auto full = _fullScreenVideo ? _zoomToScreen : _zoomToDefault;
3839 float64 nx, ny, z = (_zoom == kZoomToScreenLevel) ? full : _zoom;
3840 const auto contentSize = videoShown()
3841 ? style::ConvertScale(videoSize())
3842 : QSize(_width, _height);
3843 _oldGeometry = contentGeometry();
3844 _geometryAnimation.stop();
3845
3846 _w = contentSize.width();
3847 _h = contentSize.height();
3848 if (z >= 0) {
3849 nx = (_x - width() / 2.) / (z + 1);
3850 ny = (_y - height() / 2.) / (z + 1);
3851 } else {
3852 nx = (_x - width() / 2.) * (-z + 1);
3853 ny = (_y - height() / 2.) * (-z + 1);
3854 }
3855 _zoom = newZoom;
3856 z = (_zoom == kZoomToScreenLevel) ? full : _zoom;
3857 if (z > 0) {
3858 _w = qRound(_w * (z + 1));
3859 _h = qRound(_h * (z + 1));
3860 _x = qRound(nx * (z + 1) + width() / 2.);
3861 _y = qRound(ny * (z + 1) + height() / 2.);
3862 } else {
3863 _w = qRound(_w / (-z + 1));
3864 _h = qRound(_h / (-z + 1));
3865 _x = qRound(nx / (-z + 1) + width() / 2.);
3866 _y = qRound(ny / (-z + 1) + height() / 2.);
3867 }
3868 snapXY();
3869 if (_opengl) {
3870 _geometryAnimation.start(
3871 [=] { update(); },
3872 0.,
3873 1.,
3874 st::widgetFadeDuration/*,
3875 anim::easeOutCirc*/);
3876 }
3877 update();
3878 }
3879
entityForUserPhotos(int index) const3880 OverlayWidget::Entity OverlayWidget::entityForUserPhotos(int index) const {
3881 Expects(_userPhotosData.has_value());
3882 Expects(_session != nullptr);
3883
3884 if (index < 0 || index >= _userPhotosData->size()) {
3885 return { v::null, nullptr };
3886 }
3887 const auto id = (*_userPhotosData)[index];
3888 if (const auto photo = _session->data().photo(id)) {
3889 return { photo, nullptr };
3890 }
3891 return { v::null, nullptr };
3892 }
3893
entityForSharedMedia(int index) const3894 OverlayWidget::Entity OverlayWidget::entityForSharedMedia(int index) const {
3895 Expects(_sharedMediaData.has_value());
3896
3897 if (index < 0 || index >= _sharedMediaData->size()) {
3898 return { v::null, nullptr };
3899 }
3900 auto value = (*_sharedMediaData)[index];
3901 if (const auto photo = std::get_if<not_null<PhotoData*>>(&value)) {
3902 // Last peer photo.
3903 return { *photo, nullptr };
3904 } else if (const auto itemId = std::get_if<FullMsgId>(&value)) {
3905 return entityForItemId(*itemId);
3906 }
3907 return { v::null, nullptr };
3908 }
3909
entityForCollage(int index) const3910 OverlayWidget::Entity OverlayWidget::entityForCollage(int index) const {
3911 Expects(_collageData.has_value());
3912 Expects(_session != nullptr);
3913
3914 const auto &items = _collageData->items;
3915 if (!_message || index < 0 || index >= items.size()) {
3916 return { v::null, nullptr };
3917 }
3918 if (const auto document = std::get_if<DocumentData*>(&items[index])) {
3919 return { *document, _message };
3920 } else if (const auto photo = std::get_if<PhotoData*>(&items[index])) {
3921 return { *photo, _message };
3922 }
3923 return { v::null, nullptr };
3924 }
3925
entityForItemId(const FullMsgId & itemId) const3926 OverlayWidget::Entity OverlayWidget::entityForItemId(const FullMsgId &itemId) const {
3927 Expects(_session != nullptr);
3928
3929 if (const auto item = _session->data().message(itemId)) {
3930 if (const auto media = item->media()) {
3931 if (const auto photo = media->photo()) {
3932 return { photo, item };
3933 } else if (const auto document = media->document()) {
3934 return { document, item };
3935 }
3936 }
3937 return { v::null, item };
3938 }
3939 return { v::null, nullptr };
3940 }
3941
entityByIndex(int index) const3942 OverlayWidget::Entity OverlayWidget::entityByIndex(int index) const {
3943 if (_sharedMediaData) {
3944 return entityForSharedMedia(index);
3945 } else if (_userPhotosData) {
3946 return entityForUserPhotos(index);
3947 } else if (_collageData) {
3948 return entityForCollage(index);
3949 }
3950 return { v::null, nullptr };
3951 }
3952
setContext(std::variant<v::null_t,not_null<HistoryItem * >,not_null<PeerData * >> context)3953 void OverlayWidget::setContext(
3954 std::variant<
3955 v::null_t,
3956 not_null<HistoryItem*>,
3957 not_null<PeerData*>> context) {
3958 if (const auto item = std::get_if<not_null<HistoryItem*>>(&context)) {
3959 _message = (*item);
3960 _history = _message->history();
3961 _peer = _history->peer;
3962 } else if (const auto peer = std::get_if<not_null<PeerData*>>(&context)) {
3963 _peer = *peer;
3964 _history = _peer->owner().history(_peer);
3965 _message = nullptr;
3966 } else {
3967 _message = nullptr;
3968 _history = nullptr;
3969 _peer = nullptr;
3970 }
3971 _migrated = nullptr;
3972 if (_history) {
3973 if (_history->peer->migrateFrom()) {
3974 _migrated = _history->owner().history(_history->peer->migrateFrom());
3975 } else if (_history->peer->migrateTo()) {
3976 _migrated = _history;
3977 _history = _history->owner().history(_history->peer->migrateTo());
3978 }
3979 }
3980 _user = _peer ? _peer->asUser() : nullptr;
3981 }
3982
setSession(not_null<Main::Session * > session)3983 void OverlayWidget::setSession(not_null<Main::Session*> session) {
3984 if (_session == session) {
3985 return;
3986 }
3987
3988 clearSession();
3989 _session = session;
3990 _widget->setWindowIcon(Window::CreateIcon(session));
3991
3992 session->downloaderTaskFinished(
3993 ) | rpl::start_with_next([=] {
3994 if (!isHidden()) {
3995 updateControls();
3996 checkForSaveLoaded();
3997 }
3998 }, _sessionLifetime);
3999
4000 base::ObservableViewer(
4001 session->documentUpdated
4002 ) | rpl::start_with_next([=](DocumentData *document) {
4003 if (!isHidden()) {
4004 documentUpdated(document);
4005 }
4006 }, _sessionLifetime);
4007
4008 session->data().itemIdChanged(
4009 ) | rpl::start_with_next([=](const Data::Session::IdChange &change) {
4010 changingMsgId(change.item, change.oldId);
4011 }, _sessionLifetime);
4012
4013 session->data().itemRemoved(
4014 ) | rpl::filter([=](not_null<const HistoryItem*> item) {
4015 return (_message == item);
4016 }) | rpl::start_with_next([=] {
4017 close();
4018 clearSession();
4019 }, _sessionLifetime);
4020
4021 session->account().sessionChanges(
4022 ) | rpl::start_with_next([=] {
4023 clearSession();
4024 }, _sessionLifetime);
4025 }
4026
moveToNext(int delta)4027 bool OverlayWidget::moveToNext(int delta) {
4028 if (!_index) {
4029 return false;
4030 }
4031 auto newIndex = *_index + delta;
4032 return moveToEntity(entityByIndex(newIndex), delta);
4033 }
4034
moveToEntity(const Entity & entity,int preloadDelta)4035 bool OverlayWidget::moveToEntity(const Entity &entity, int preloadDelta) {
4036 if (v::is_null(entity.data) && !entity.item) {
4037 return false;
4038 }
4039 if (const auto item = entity.item) {
4040 setContext(item);
4041 } else if (_peer) {
4042 setContext(_peer);
4043 } else {
4044 setContext(v::null);
4045 }
4046 clearStreaming();
4047 _streamingStartPaused = false;
4048 if (auto photo = std::get_if<not_null<PhotoData*>>(&entity.data)) {
4049 displayPhoto(*photo);
4050 } else if (auto document = std::get_if<not_null<DocumentData*>>(&entity.data)) {
4051 displayDocument(*document);
4052 } else {
4053 displayDocument(nullptr);
4054 }
4055 preloadData(preloadDelta);
4056 return true;
4057 }
4058
preloadData(int delta)4059 void OverlayWidget::preloadData(int delta) {
4060 if (!_index) {
4061 return;
4062 }
4063 auto from = *_index + (delta ? -delta : -1);
4064 auto till = *_index + (delta ? delta * kPreloadCount : 1);
4065 if (from > till) std::swap(from, till);
4066
4067 auto photos = base::flat_set<std::shared_ptr<Data::PhotoMedia>>();
4068 auto documents = base::flat_set<std::shared_ptr<Data::DocumentMedia>>();
4069 for (auto index = from; index != till + 1; ++index) {
4070 auto entity = entityByIndex(index);
4071 if (auto photo = std::get_if<not_null<PhotoData*>>(&entity.data)) {
4072 const auto [i, ok] = photos.emplace((*photo)->createMediaView());
4073 (*i)->wanted(Data::PhotoSize::Small, fileOrigin(entity));
4074 (*photo)->load(fileOrigin(entity), LoadFromCloudOrLocal, true);
4075 } else if (auto document = std::get_if<not_null<DocumentData*>>(
4076 &entity.data)) {
4077 const auto [i, ok] = documents.emplace(
4078 (*document)->createMediaView());
4079 (*i)->thumbnailWanted(fileOrigin(entity));
4080 if (!(*i)->canBePlayed()) {
4081 (*i)->automaticLoad(fileOrigin(entity), entity.item);
4082 }
4083 }
4084 }
4085 _preloadPhotos = std::move(photos);
4086 _preloadDocuments = std::move(documents);
4087 }
4088
handleMousePress(QPoint position,Qt::MouseButton button)4089 void OverlayWidget::handleMousePress(
4090 QPoint position,
4091 Qt::MouseButton button) {
4092 updateOver(position);
4093 if (_menu || !_receiveMouse) {
4094 return;
4095 }
4096
4097 ClickHandler::pressed();
4098
4099 if (button == Qt::LeftButton) {
4100 _down = OverNone;
4101 if (!ClickHandler::getPressed()) {
4102 if (_over == OverLeftNav && moveToNext(-1)) {
4103 _lastAction = position;
4104 } else if (_over == OverRightNav && moveToNext(1)) {
4105 _lastAction = position;
4106 } else if (_over == OverName
4107 || _over == OverDate
4108 || _over == OverHeader
4109 || _over == OverSave
4110 || _over == OverRotate
4111 || _over == OverIcon
4112 || _over == OverMore
4113 || _over == OverClose
4114 || _over == OverVideo) {
4115 _down = _over;
4116 } else if (!_saveMsg.contains(position) || !_saveMsgStarted) {
4117 _pressed = true;
4118 _dragging = 0;
4119 updateCursor();
4120 _mStart = position;
4121 _xStart = _x;
4122 _yStart = _y;
4123 }
4124 }
4125 } else if (button == Qt::MiddleButton) {
4126 zoomReset();
4127 }
4128 activateControls();
4129 }
4130
handleDoubleClick(QPoint position,Qt::MouseButton button)4131 bool OverlayWidget::handleDoubleClick(
4132 QPoint position,
4133 Qt::MouseButton button) {
4134 updateOver(position);
4135
4136 if (_over != OverVideo || !_streamed || button != Qt::LeftButton) {
4137 return false;
4138 }
4139 playbackToggleFullScreen();
4140 playbackPauseResume();
4141 return true;
4142 }
4143
snapXY()4144 void OverlayWidget::snapXY() {
4145 int32 xmin = width() - _w, xmax = 0;
4146 int32 ymin = height() - _h, ymax = 0;
4147 if (xmin > (width() - _w) / 2) xmin = (width() - _w) / 2;
4148 if (xmax < (width() - _w) / 2) xmax = (width() - _w) / 2;
4149 if (ymin > (height() - _h) / 2) ymin = (height() - _h) / 2;
4150 if (ymax < (height() - _h) / 2) ymax = (height() - _h) / 2;
4151 if (_x < xmin) _x = xmin;
4152 if (_x > xmax) _x = xmax;
4153 if (_y < ymin) _y = ymin;
4154 if (_y > ymax) _y = ymax;
4155 }
4156
handleMouseMove(QPoint position)4157 void OverlayWidget::handleMouseMove(QPoint position) {
4158 updateOver(position);
4159 if (_lastAction.x() >= 0
4160 && ((position - _lastAction).manhattanLength()
4161 >= st::mediaviewDeltaFromLastAction)) {
4162 _lastAction = QPoint(-st::mediaviewDeltaFromLastAction, -st::mediaviewDeltaFromLastAction);
4163 }
4164 if (_pressed) {
4165 if (!_dragging
4166 && ((position - _mStart).manhattanLength()
4167 >= QApplication::startDragDistance())) {
4168 _dragging = QRect(_x, _y, _w, _h).contains(_mStart) ? 1 : -1;
4169 if (_dragging > 0) {
4170 if (_w > width() || _h > height()) {
4171 setCursor(style::cur_sizeall);
4172 } else {
4173 setCursor(style::cur_default);
4174 }
4175 }
4176 }
4177 if (_dragging > 0) {
4178 _x = _xStart + (position - _mStart).x();
4179 _y = _yStart + (position - _mStart).y();
4180 snapXY();
4181 update();
4182 }
4183 }
4184 }
4185
updateOverRect(OverState state)4186 void OverlayWidget::updateOverRect(OverState state) {
4187 switch (state) {
4188 case OverLeftNav: update(_leftNav); break;
4189 case OverRightNav: update(_rightNav); break;
4190 case OverName: update(_nameNav); break;
4191 case OverDate: update(_dateNav); break;
4192 case OverSave: update(_saveNavIcon); break;
4193 case OverRotate: update(_rotateNavIcon); break;
4194 case OverIcon: update(_docIconRect); break;
4195 case OverHeader: update(_headerNav); break;
4196 case OverClose: update(_closeNav); break;
4197 case OverMore: update(_moreNavIcon); break;
4198 }
4199 }
4200
updateOverState(OverState newState)4201 bool OverlayWidget::updateOverState(OverState newState) {
4202 bool result = true;
4203 if (_over != newState) {
4204 if (newState == OverMore && !_ignoringDropdown) {
4205 _dropdownShowTimer.callOnce(0);
4206 } else {
4207 _dropdownShowTimer.cancel();
4208 }
4209 updateOverRect(_over);
4210 updateOverRect(newState);
4211 if (_over != OverNone) {
4212 _animations[_over] = crl::now();
4213 const auto i = _animationOpacities.find(_over);
4214 if (i != end(_animationOpacities)) {
4215 i->second.start(0);
4216 } else {
4217 _animationOpacities.emplace(_over, anim::value(1, 0));
4218 }
4219 if (!_stateAnimation.animating()) {
4220 _stateAnimation.start();
4221 }
4222 } else {
4223 result = false;
4224 }
4225 _over = newState;
4226 if (newState != OverNone) {
4227 _animations[_over] = crl::now();
4228 const auto i = _animationOpacities.find(_over);
4229 if (i != end(_animationOpacities)) {
4230 i->second.start(1);
4231 } else {
4232 _animationOpacities.emplace(_over, anim::value(0, 1));
4233 }
4234 if (!_stateAnimation.animating()) {
4235 _stateAnimation.start();
4236 }
4237 }
4238 updateCursor();
4239 }
4240 return result;
4241 }
4242
updateOver(QPoint pos)4243 void OverlayWidget::updateOver(QPoint pos) {
4244 ClickHandlerPtr lnk;
4245 ClickHandlerHost *lnkhost = nullptr;
4246 if (_saveMsgStarted && _saveMsg.contains(pos)) {
4247 auto textState = _saveMsgText.getState(pos - _saveMsg.topLeft() - QPoint(st::mediaviewSaveMsgPadding.left(), st::mediaviewSaveMsgPadding.top()), _saveMsg.width() - st::mediaviewSaveMsgPadding.left() - st::mediaviewSaveMsgPadding.right());
4248 lnk = textState.link;
4249 lnkhost = this;
4250 } else if (_captionRect.contains(pos)) {
4251 auto textState = _caption.getState(pos - _captionRect.topLeft(), _captionRect.width());
4252 lnk = textState.link;
4253 lnkhost = this;
4254 } else if (_groupThumbs && _groupThumbsRect.contains(pos)) {
4255 const auto point = pos - QPoint(_groupThumbsLeft, _groupThumbsTop);
4256 lnk = _groupThumbs->getState(point);
4257 lnkhost = this;
4258 }
4259
4260
4261 // retina
4262 if (pos.x() == width()) {
4263 pos.setX(pos.x() - 1);
4264 }
4265 if (pos.y() == height()) {
4266 pos.setY(pos.y() - 1);
4267 }
4268
4269 ClickHandler::setActive(lnk, lnkhost);
4270
4271 if (_pressed || _dragging) return;
4272
4273 if (_fullScreenVideo) {
4274 updateOverState(OverVideo);
4275 } else if (_leftNavVisible && _leftNav.contains(pos)) {
4276 updateOverState(OverLeftNav);
4277 } else if (_rightNavVisible && _rightNav.contains(pos)) {
4278 updateOverState(OverRightNav);
4279 } else if (_from && _nameNav.contains(pos)) {
4280 updateOverState(OverName);
4281 } else if (_message && _message->isRegular() && _dateNav.contains(pos)) {
4282 updateOverState(OverDate);
4283 } else if (_headerHasLink && _headerNav.contains(pos)) {
4284 updateOverState(OverHeader);
4285 } else if (_saveVisible && _saveNav.contains(pos)) {
4286 updateOverState(OverSave);
4287 } else if (_rotateVisible && _rotateNav.contains(pos)) {
4288 updateOverState(OverRotate);
4289 } else if (_document && documentBubbleShown() && _docIconRect.contains(pos)) {
4290 updateOverState(OverIcon);
4291 } else if (_moreNav.contains(pos)) {
4292 updateOverState(OverMore);
4293 } else if (_closeNav.contains(pos)) {
4294 updateOverState(OverClose);
4295 } else if (documentContentShown() && finalContentRect().contains(pos)) {
4296 if ((_document->isVideoFile() || _document->isVideoMessage()) && _streamed) {
4297 updateOverState(OverVideo);
4298 } else if (!_streamed && !_documentMedia->loaded()) {
4299 updateOverState(OverIcon);
4300 } else if (_over != OverNone) {
4301 updateOverState(OverNone);
4302 }
4303 } else if (_over != OverNone) {
4304 updateOverState(OverNone);
4305 }
4306 }
4307
handleMouseRelease(QPoint position,Qt::MouseButton button)4308 void OverlayWidget::handleMouseRelease(
4309 QPoint position,
4310 Qt::MouseButton button) {
4311 updateOver(position);
4312
4313 if (const auto activated = ClickHandler::unpressed()) {
4314 if (activated->dragText() == qstr("internal:show_saved_message")) {
4315 showSaveMsgFile();
4316 return;
4317 }
4318 // There may be a mention / hashtag / bot command link.
4319 // For now activate account for all activated links.
4320 // findWindow() will activate account.
4321 ActivateClickHandler(_widget, activated, {
4322 button,
4323 QVariant::fromValue(ClickHandlerContext{
4324 .itemId = _message ? _message->fullId() : FullMsgId(),
4325 .sessionWindow = base::make_weak(findWindow()),
4326 })
4327 });
4328 return;
4329 }
4330
4331 if (_over == OverName && _down == OverName) {
4332 if (_from) {
4333 close();
4334 Ui::showPeerProfile(_from);
4335 }
4336 } else if (_over == OverDate && _down == OverDate) {
4337 toMessage();
4338 } else if (_over == OverHeader && _down == OverHeader) {
4339 showMediaOverview();
4340 } else if (_over == OverSave && _down == OverSave) {
4341 downloadMedia();
4342 } else if (_over == OverRotate && _down == OverRotate) {
4343 playbackControlsRotate();
4344 } else if (_over == OverIcon && _down == OverIcon) {
4345 handleDocumentClick();
4346 } else if (_over == OverMore && _down == OverMore) {
4347 InvokeQueued(_widget, [=] { showDropdown(); });
4348 } else if (_over == OverClose && _down == OverClose) {
4349 close();
4350 } else if (_over == OverVideo && _down == OverVideo) {
4351 if (_streamed) {
4352 playbackPauseResume();
4353 }
4354 } else if (_pressed) {
4355 if (_dragging) {
4356 if (_dragging > 0) {
4357 _x = _xStart + (position - _mStart).x();
4358 _y = _yStart + (position - _mStart).y();
4359 snapXY();
4360 update();
4361 }
4362 _dragging = 0;
4363 setCursor(style::cur_default);
4364 } else if ((position - _lastAction).manhattanLength()
4365 >= st::mediaviewDeltaFromLastAction) {
4366 if (_themePreviewShown) {
4367 if (!_themePreviewRect.contains(position)) {
4368 close();
4369 }
4370 } else if (!_document
4371 || documentContentShown()
4372 || !documentBubbleShown()
4373 || !_docRect.contains(position)) {
4374 close();
4375 }
4376 }
4377 _pressed = false;
4378 }
4379 _down = OverNone;
4380 if (!isHidden()) {
4381 activateControls();
4382 }
4383 }
4384
handleContextMenu(std::optional<QPoint> position)4385 bool OverlayWidget::handleContextMenu(std::optional<QPoint> position) {
4386 if (position && !QRect(_x, _y, _w, _h).contains(*position)) {
4387 return false;
4388 }
4389 _menu = base::make_unique_q<Ui::PopupMenu>(
4390 _widget,
4391 st::mediaviewPopupMenu);
4392 fillContextMenuActions([&] (const QString &text, Fn<void()> handler) {
4393 _menu->addAction(text, std::move(handler));
4394 });
4395 _menu->setDestroyedCallback(crl::guard(_widget, [=] {
4396 activateControls();
4397 _receiveMouse = false;
4398 InvokeQueued(_widget, [=] { receiveMouse(); });
4399 }));
4400 _menu->popup(QCursor::pos());
4401 activateControls();
4402 return true;
4403 }
4404
handleTouchEvent(not_null<QTouchEvent * > e)4405 bool OverlayWidget::handleTouchEvent(not_null<QTouchEvent*> e) {
4406 if (e->device()->type() != base::TouchDevice::TouchScreen) {
4407 return false;
4408 } else if (e->type() == QEvent::TouchBegin
4409 && !e->touchPoints().isEmpty()
4410 && _widget->childAt(
4411 _widget->mapFromGlobal(
4412 e->touchPoints().cbegin()->screenPos().toPoint()))) {
4413 return false;
4414 }
4415 switch (e->type()) {
4416 case QEvent::TouchBegin: {
4417 if (_touchPress || e->touchPoints().isEmpty()) {
4418 break;
4419 }
4420 _touchTimer.callOnce(QApplication::startDragTime());
4421 _touchPress = true;
4422 _touchMove = _touchRightButton = false;
4423 _touchStart = e->touchPoints().cbegin()->screenPos().toPoint();
4424 } break;
4425
4426 case QEvent::TouchUpdate: {
4427 if (!_touchPress || e->touchPoints().isEmpty()) {
4428 break;
4429 }
4430 if (!_touchMove && (e->touchPoints().cbegin()->screenPos().toPoint() - _touchStart).manhattanLength() >= QApplication::startDragDistance()) {
4431 _touchMove = true;
4432 }
4433 } break;
4434
4435 case QEvent::TouchEnd: {
4436 if (!_touchPress) {
4437 break;
4438 }
4439 auto weak = Ui::MakeWeak(_widget);
4440 if (!_touchMove) {
4441 const auto button = _touchRightButton
4442 ? Qt::RightButton
4443 : Qt::LeftButton;
4444 const auto position = _widget->mapFromGlobal(_touchStart);
4445
4446 if (weak) handleMousePress(position, button);
4447 if (weak) handleMouseRelease(position, button);
4448 if (weak && _touchRightButton) {
4449 handleContextMenu(position);
4450 }
4451 } else if (_touchMove) {
4452 if ((!_leftNavVisible || !_leftNav.contains(_widget->mapFromGlobal(_touchStart))) && (!_rightNavVisible || !_rightNav.contains(_widget->mapFromGlobal(_touchStart)))) {
4453 QPoint d = (e->touchPoints().cbegin()->screenPos().toPoint() - _touchStart);
4454 if (d.x() * d.x() > d.y() * d.y() && (d.x() > st::mediaviewSwipeDistance || d.x() < -st::mediaviewSwipeDistance)) {
4455 moveToNext(d.x() > 0 ? -1 : 1);
4456 }
4457 }
4458 }
4459 if (weak) {
4460 _touchTimer.cancel();
4461 _touchPress = _touchMove = _touchRightButton = false;
4462 }
4463 } break;
4464
4465 case QEvent::TouchCancel: {
4466 _touchPress = false;
4467 _touchTimer.cancel();
4468 } break;
4469 }
4470 return true;
4471 }
4472
toggleApplicationEventFilter(bool install)4473 void OverlayWidget::toggleApplicationEventFilter(bool install) {
4474 if (!install) {
4475 _applicationEventFilter = nullptr;
4476 return;
4477 } else if (_applicationEventFilter) {
4478 return;
4479 }
4480 class Filter final : public QObject {
4481 public:
4482 explicit Filter(not_null<OverlayWidget*> owner) : _owner(owner) {
4483 }
4484
4485 private:
4486 bool eventFilter(QObject *obj, QEvent *e) override {
4487 return obj && e && _owner->filterApplicationEvent(obj, e);
4488 }
4489
4490 const not_null<OverlayWidget*> _owner;
4491
4492 };
4493
4494 _applicationEventFilter = std::make_unique<Filter>(this);
4495 qApp->installEventFilter(_applicationEventFilter.get());
4496 }
4497
filterApplicationEvent(not_null<QObject * > object,not_null<QEvent * > e)4498 bool OverlayWidget::filterApplicationEvent(
4499 not_null<QObject*> object,
4500 not_null<QEvent*> e) {
4501 const auto type = e->type();
4502 if (type == QEvent::ShortcutOverride) {
4503 const auto keyEvent = static_cast<QKeyEvent*>(e.get());
4504 const auto ctrl = keyEvent->modifiers().testFlag(Qt::ControlModifier);
4505 if (keyEvent->key() == Qt::Key_F && ctrl && _streamed) {
4506 playbackToggleFullScreen();
4507 }
4508 return true;
4509 } else if (type == QEvent::MouseMove
4510 || type == QEvent::MouseButtonPress
4511 || type == QEvent::MouseButtonRelease) {
4512 if (object->isWidgetType()
4513 && _widget->isAncestorOf(static_cast<QWidget*>(object.get()))) {
4514 const auto mouseEvent = static_cast<QMouseEvent*>(e.get());
4515 const auto mousePosition = _widget->mapFromGlobal(
4516 mouseEvent->globalPos());
4517 const auto delta = (mousePosition - _lastMouseMovePos);
4518 auto activate = delta.manhattanLength()
4519 >= st::mediaviewDeltaFromLastAction;
4520 if (activate) {
4521 _lastMouseMovePos = mousePosition;
4522 }
4523 if (type == QEvent::MouseButtonPress) {
4524 _mousePressed = true;
4525 activate = true;
4526 } else if (type == QEvent::MouseButtonRelease) {
4527 _mousePressed = false;
4528 activate = true;
4529 }
4530 if (activate) {
4531 activateControls();
4532 }
4533 }
4534 }
4535 return false;
4536 }
4537
applyHideWindowWorkaround()4538 void OverlayWidget::applyHideWindowWorkaround() {
4539 // QOpenGLWidget can't properly destroy a child widget if it is hidden
4540 // exactly after that, the child is cached in the backing store.
4541 // So on next paint we force full backing store repaint.
4542 if (_opengl && !isHidden() && !_hideWorkaround) {
4543 _hideWorkaround = std::make_unique<Ui::RpWidget>(_widget);
4544 const auto raw = _hideWorkaround.get();
4545 raw->setGeometry(_widget->rect());
4546 raw->show();
4547 raw->paintRequest(
4548 ) | rpl::start_with_next([=] {
4549 if (_hideWorkaround.get() == raw) {
4550 _hideWorkaround.release();
4551 }
4552 QPainter(raw).fillRect(raw->rect(), QColor(0, 1, 0, 1));
4553 crl::on_main(raw, [=] {
4554 delete raw;
4555 });
4556 }, raw->lifetime());
4557 raw->update();
4558
4559 if (Platform::IsWindows()) {
4560 Ui::Platform::UpdateOverlayed(_widget);
4561 }
4562 }
4563 }
4564
findWindow(bool switchTo) const4565 Window::SessionController *OverlayWidget::findWindow(bool switchTo) const {
4566 if (!_session) {
4567 return nullptr;
4568 }
4569
4570 const auto window = _window.get();
4571 if (window) {
4572 if (const auto controller = window->sessionController()) {
4573 if (&controller->session() == _session) {
4574 return controller;
4575 }
4576 }
4577 }
4578
4579 const auto &active = _session->windows();
4580 if (!active.empty()) {
4581 return active.front();
4582 } else if (window && switchTo) {
4583 Window::SessionController *controllerPtr = nullptr;
4584 window->invokeForSessionController(
4585 &_session->account(),
4586 [&](not_null<Window::SessionController*> newController) {
4587 controllerPtr = newController;
4588 });
4589 return controllerPtr;
4590 }
4591
4592 return nullptr;
4593 }
4594
4595 // #TODO unite and check
clearBeforeHide()4596 void OverlayWidget::clearBeforeHide() {
4597 _sharedMedia = nullptr;
4598 _sharedMediaData = std::nullopt;
4599 _sharedMediaDataKey = std::nullopt;
4600 _userPhotos = nullptr;
4601 _userPhotosData = std::nullopt;
4602 _collage = nullptr;
4603 _collageData = std::nullopt;
4604 assignMediaPointer(nullptr);
4605 _preloadPhotos.clear();
4606 _preloadDocuments.clear();
4607 if (_menu) {
4608 _menu->hideMenu(true);
4609 }
4610 _controlsHideTimer.cancel();
4611 _controlsState = ControlsShown;
4612 _controlsOpacity = anim::value(1, 1);
4613 _groupThumbs = nullptr;
4614 _groupThumbsRect = QRect();
4615 for (const auto child : _widget->children()) {
4616 if (child->isWidgetType() && _hideWorkaround.get() != child) {
4617 static_cast<QWidget*>(child)->hide();
4618 }
4619 }
4620 }
4621
clearAfterHide()4622 void OverlayWidget::clearAfterHide() {
4623 clearStreaming();
4624 destroyThemePreview();
4625 _radial.stop();
4626 _staticContent = QImage();
4627 _themePreview = nullptr;
4628 _themeApply.destroyDelayed();
4629 _themeCancel.destroyDelayed();
4630 _themeShare.destroyDelayed();
4631 }
4632
receiveMouse()4633 void OverlayWidget::receiveMouse() {
4634 _receiveMouse = true;
4635 }
4636
showDropdown()4637 void OverlayWidget::showDropdown() {
4638 _dropdown->clearActions();
4639 fillContextMenuActions([&] (const QString &text, Fn<void()> handler) {
4640 _dropdown->addAction(text, std::move(handler));
4641 });
4642 _dropdown->moveToRight(0, height() - _dropdown->height());
4643 _dropdown->showAnimated(Ui::PanelAnimation::Origin::BottomRight);
4644 _dropdown->setFocus();
4645 }
4646
handleTouchTimer()4647 void OverlayWidget::handleTouchTimer() {
4648 _touchRightButton = true;
4649 }
4650
updateImage()4651 void OverlayWidget::updateImage() {
4652 update(_saveMsg);
4653 }
4654
findCurrent()4655 void OverlayWidget::findCurrent() {
4656 using namespace rpl::mappers;
4657 if (_sharedMediaData) {
4658 _index = _message
4659 ? _sharedMediaData->indexOf(_message->fullId())
4660 : _photo ? _sharedMediaData->indexOf(_photo) : std::nullopt;
4661 _fullIndex = _sharedMediaData->skippedBefore()
4662 ? (_index | func::add(*_sharedMediaData->skippedBefore()))
4663 : std::nullopt;
4664 _fullCount = _sharedMediaData->fullCount();
4665 } else if (_userPhotosData) {
4666 _index = _photo ? _userPhotosData->indexOf(_photo->id) : std::nullopt;
4667 _fullIndex = _userPhotosData->skippedBefore()
4668 ? (_index | func::add(*_userPhotosData->skippedBefore()))
4669 : std::nullopt;
4670 _fullCount = _userPhotosData->fullCount();
4671 } else if (_collageData) {
4672 const auto item = _photo ? WebPageCollage::Item(_photo) : _document;
4673 const auto &items = _collageData->items;
4674 const auto i = ranges::find(items, item);
4675 _index = (i != end(items))
4676 ? std::make_optional(int(i - begin(items)))
4677 : std::nullopt;
4678 _fullIndex = _index;
4679 _fullCount = items.size();
4680 } else {
4681 _index = _fullIndex = _fullCount = std::nullopt;
4682 }
4683 }
4684
updateHeader()4685 void OverlayWidget::updateHeader() {
4686 auto index = _fullIndex ? *_fullIndex : -1;
4687 auto count = _fullCount ? *_fullCount : -1;
4688 if (index >= 0 && index < count && count > 1) {
4689 if (_document) {
4690 _headerText = tr::lng_mediaview_file_n_of_amount(
4691 tr::now,
4692 lt_file,
4693 (_document->filename().isEmpty()
4694 ? tr::lng_mediaview_doc_image(tr::now)
4695 : _document->filename()),
4696 lt_n,
4697 QString::number(index + 1),
4698 lt_amount,
4699 QString::number(count));
4700 } else {
4701 _headerText = tr::lng_mediaview_n_of_amount(
4702 tr::now,
4703 lt_n,
4704 QString::number(index + 1),
4705 lt_amount,
4706 QString::number(count));
4707 }
4708 } else {
4709 if (_document) {
4710 _headerText = _document->filename().isEmpty() ? tr::lng_mediaview_doc_image(tr::now) : _document->filename();
4711 } else if (_message) {
4712 _headerText = tr::lng_mediaview_single_photo(tr::now);
4713 } else if (_user) {
4714 _headerText = tr::lng_mediaview_profile_photo(tr::now);
4715 } else if ((_history && _history->channelId() && !_history->isMegagroup())
4716 || (_peer && _peer->isChannel() && !_peer->isMegagroup())) {
4717 _headerText = tr::lng_mediaview_channel_photo(tr::now);
4718 } else if (_peer) {
4719 _headerText = tr::lng_mediaview_group_photo(tr::now);
4720 } else {
4721 _headerText = tr::lng_mediaview_single_photo(tr::now);
4722 }
4723 }
4724 _headerHasLink = computeOverviewType() != std::nullopt;
4725 auto hwidth = st::mediaviewThickFont->width(_headerText);
4726 if (hwidth > width() / 3) {
4727 hwidth = width() / 3;
4728 _headerText = st::mediaviewThickFont->elided(_headerText, hwidth, Qt::ElideMiddle);
4729 }
4730 _headerNav = QRect(st::mediaviewTextLeft, height() - st::mediaviewHeaderTop, hwidth, st::mediaviewThickFont->height);
4731 }
4732
overLevel(OverState control) const4733 float64 OverlayWidget::overLevel(OverState control) const {
4734 auto i = _animationOpacities.find(control);
4735 return (i == end(_animationOpacities))
4736 ? (_over == control ? 1. : 0.)
4737 : i->second.current();
4738 }
4739
4740 } // namespace View
4741 } // namespace Media
4742