From 2a1e3c44534d0f28e22a1951b275d9c6ce1d6c17 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 14 Feb 2023 17:27:52 +0400 Subject: [PATCH] Initial implementation of windowed media viewer on macOS. --- Telegram/CMakeLists.txt | 6 + .../media/view/media_view_overlay_widget.cpp | 58 ++-- .../media/view/media_view_overlay_widget.h | 10 +- .../platform/linux/overlay_widget_linux.h | 20 ++ .../platform/mac/overlay_widget_mac.h | 37 +++ .../platform/mac/overlay_widget_mac.mm | 261 ++++++++++++++++++ .../platform/platform_overlay_widget.cpp | 34 +++ .../platform/platform_overlay_widget.h | 66 +++++ .../platform/win/overlay_widget_win.h | 20 ++ Telegram/build/prepare/prepare.py | 2 +- Telegram/lib_ui | 2 +- 11 files changed, 475 insertions(+), 41 deletions(-) create mode 100644 Telegram/SourceFiles/platform/linux/overlay_widget_linux.h create mode 100644 Telegram/SourceFiles/platform/mac/overlay_widget_mac.h create mode 100644 Telegram/SourceFiles/platform/mac/overlay_widget_mac.mm create mode 100644 Telegram/SourceFiles/platform/platform_overlay_widget.cpp create mode 100644 Telegram/SourceFiles/platform/platform_overlay_widget.h create mode 100644 Telegram/SourceFiles/platform/win/overlay_widget_win.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index d647177bd..c0d2a844e 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -1083,6 +1083,7 @@ PRIVATE platform/linux/notifications_manager_linux_dummy.cpp platform/linux/notifications_manager_linux.cpp platform/linux/notifications_manager_linux.h + platform/linux/overlay_widget_linux.h platform/linux/specific_linux.cpp platform/linux/specific_linux.h platform/linux/tray_linux.cpp @@ -1098,6 +1099,8 @@ PRIVATE platform/mac/main_window_mac.h platform/mac/notifications_manager_mac.mm platform/mac/notifications_manager_mac.h + platform/mac/overlay_widget_mac.h + platform/mac/overlay_widget_mac.mm platform/mac/specific_mac.mm platform/mac/specific_mac.h platform/mac/specific_mac_p.mm @@ -1135,6 +1138,7 @@ PRIVATE platform/win/main_window_win.h platform/win/notifications_manager_win.cpp platform/win/notifications_manager_win.h + platform/win/overlay_widget_win.h platform/win/specific_win.cpp platform/win/specific_win.h platform/win/tray_win.cpp @@ -1154,6 +1158,8 @@ PRIVATE platform/platform_integration.h platform/platform_main_window.h platform/platform_notifications_manager.h + platform/platform_overlay_widget.cpp + platform/platform_overlay_widget.h platform/platform_specific.h platform/platform_tray.h platform/platform_window_title.h diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index 51e70ec0c..e0a8c35a1 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -81,6 +81,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session.h" #include "main/main_session_settings.h" #include "layout/layout_document_generic_preview.h" +#include "platform/platform_overlay_widget.h" #include "storage/file_download.h" #include "storage/storage_account.h" #include "calls/calls_instance.h" @@ -298,15 +299,12 @@ OverlayWidget::PipWrap::PipWrap( } OverlayWidget::OverlayWidget() -: _supportWindowMode(!Platform::IsMac()) +: _supportWindowMode(true) , _wrap(std::make_unique()) , _window(_wrap->window()) -#ifndef Q_OS_MAC -, _controls(Ui::Platform::SetupSeparateTitleControls( - _window.get(), - st::callTitle, - [=](bool maximized) { toggleFullScreen(maximized); })) -#endif +, _helper(Platform::CreateOverlayWidgetHelper(_window.get(), [=](bool maximized) { + toggleFullScreen(maximized); +})) , _body(_wrap->widget()) , _surface( Ui::GL::CreateSurface(_body, chooseRenderer(_wrap->backend()))) @@ -489,22 +487,17 @@ OverlayWidget::OverlayWidget() } void OverlayWidget::orderWidgets() { -#ifndef Q_OS_MAC - _controls->wrap.raise(); -#endif // !Q_OS_MAC + _helper->orderWidgets(); } void OverlayWidget::setupWindow() { _window->setBodyTitleArea([=](QPoint widgetPoint) { using Flag = Ui::WindowTitleHitTestFlag; - if (!_windowed || !_widget->rect().contains(widgetPoint)) { + if (!_windowed + || !_widget->rect().contains(widgetPoint) + || _helper->skipTitleHitTest(widgetPoint)) { return Flag::None | Flag(0); } -#ifndef Q_OS_MAC - if (_controls->controls.geometry().contains(widgetPoint)) { - return Flag::None | Flag(0); - } -#endif // !Q_OS_MAC const auto inControls = (_over != OverNone) && (_over != OverVideo); if (inControls) { return Flag::None | Flag(0); @@ -514,7 +507,7 @@ void OverlayWidget::setupWindow() { if (_supportWindowMode) { const auto callback = [=](Qt::WindowState state) { - if (state == Qt::WindowMinimized) { + if (state == Qt::WindowMinimized || Platform::IsMac()) { return; } else if (state == Qt::WindowFullScreen) { _fullscreen = true; @@ -1600,7 +1593,15 @@ void OverlayWidget::close() { } void OverlayWidget::toggleFullScreen(bool fullscreen) { - if (fullscreen) { + if constexpr (Platform::IsMac()) { + _fullscreen = fullscreen; + _windowed = !fullscreen; + _helper->beforeShow(_fullscreen); + if (_fullscreen) { + moveToScreen(); + } + _helper->afterShow(_fullscreen); + } else if (fullscreen) { _window->showFullScreen(); } else { _window->showNormal(); @@ -1697,14 +1698,7 @@ void OverlayWidget::toMessage() { } void OverlayWidget::notifyFileDialogShown(bool shown) { - if (!_fullscreen || (shown && isHidden())) { - return; - } - if (shown) { - Ui::Platform::BringToBack(_window); - } else { - Ui::Platform::ShowOverAll(_window); - } + _helper->notifyFileDialogShown(shown); } void OverlayWidget::saveAs() { @@ -2871,6 +2865,7 @@ void OverlayWidget::updateThemePreviewGeometry() { void OverlayWidget::displayFinished() { updateControls(); if (isHidden()) { + _helper->beforeShow(_fullscreen); moveToScreen(); //setAttribute(Qt::WA_DontShowOnScreen); //OverlayParent::setVisibleHook(true); @@ -2887,18 +2882,14 @@ void OverlayWidget::displayFinished() { void OverlayWidget::showAndActivate() { _body->show(); - if constexpr (Platform::IsMac()) { - _window->show(); - } else if (_windowed) { + if (_windowed || Platform::IsMac()) { _window->showNormal(); } else if (_fullscreen) { _window->showFullScreen(); } else { _window->showMaximized(); } - if (_fullscreen) { - Ui::Platform::ShowOverAll(_window); - } + _helper->afterShow(_fullscreen); activate(); } @@ -4732,7 +4723,8 @@ void OverlayWidget::handleMouseRelease( } else if (_over == OverMore && _down == OverMore) { InvokeQueued(_widget, [=] { showDropdown(); }); } else if (_over == OverClose && _down == OverClose) { - close(); + //close(); + toggleFullScreen(!_fullscreen); } else if (_over == OverVideo && _down == OverVideo) { if (_streamed) { playbackPauseResume(); diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.h b/Telegram/SourceFiles/media/view/media_view_overlay_widget.h index 5fc68be69..1ef65da59 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.h +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.h @@ -40,9 +40,9 @@ struct ChosenRenderer; enum class Backend; } // namespace Ui::GL -namespace Ui::Platform { -struct SeparateTitleControls; -} // namespace Ui::Platform +namespace Platform { +class OverlayWidgetHelper; +} // namespace Platform namespace Window { namespace Theme { @@ -455,9 +455,7 @@ private: bool _opengl = false; const std::unique_ptr _wrap; const not_null _window; -#ifndef Q_OS_MAC - const std::unique_ptr _controls; -#endif + const std::unique_ptr _helper; const not_null _body; const std::unique_ptr _surface; const not_null _widget; diff --git a/Telegram/SourceFiles/platform/linux/overlay_widget_linux.h b/Telegram/SourceFiles/platform/linux/overlay_widget_linux.h new file mode 100644 index 000000000..6430f4b20 --- /dev/null +++ b/Telegram/SourceFiles/platform/linux/overlay_widget_linux.h @@ -0,0 +1,20 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +namespace Platform { + +inline std::unique_ptr CreateOverlayWidgetHelper( + not_null window, + Fn maximize) { + return std::make_unique( + window, + std::move(maximize)); +} + +} // namespace Platform diff --git a/Telegram/SourceFiles/platform/mac/overlay_widget_mac.h b/Telegram/SourceFiles/platform/mac/overlay_widget_mac.h new file mode 100644 index 000000000..077fc5412 --- /dev/null +++ b/Telegram/SourceFiles/platform/mac/overlay_widget_mac.h @@ -0,0 +1,37 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "platform/platform_overlay_widget.h" + +namespace Platform { + +class MacOverlayWidgetHelper final : public OverlayWidgetHelper { +public: + MacOverlayWidgetHelper( + not_null window, + Fn maximize); + ~MacOverlayWidgetHelper(); + + void beforeShow(bool fullscreen) override; + void afterShow(bool fullscreen) override; + void notifyFileDialogShown(bool shown) override; + +private: + struct Data; + + void activate(int button); // NSWindowButton + void resolveNative(); + void updateStyles(bool fullscreen); + void refreshButtons(bool fullscreen); + + std::unique_ptr _data; + +}; + +} // namespace Platform diff --git a/Telegram/SourceFiles/platform/mac/overlay_widget_mac.mm b/Telegram/SourceFiles/platform/mac/overlay_widget_mac.mm new file mode 100644 index 000000000..8c49ddf7d --- /dev/null +++ b/Telegram/SourceFiles/platform/mac/overlay_widget_mac.mm @@ -0,0 +1,261 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "platform/mac/overlay_widget_mac.h" + +#include "ui/widgets/rp_window.h" + +#include +#include + +@interface ButtonHandler : NSObject { +} + +- (instancetype) initWithCallback:(Fn)callback; +- (void) close:(id) sender; +- (void) miniaturize:(id) sender; +- (void) zoom:(id) sender; + +@end // @interface ButtonHandler + +@implementation ButtonHandler { + Fn _callback; +} + +- (instancetype) initWithCallback:(Fn)callback { + _callback = std::move(callback); + return [super init]; +} + +- (void) close:(id) sender { + _callback(NSWindowCloseButton); +} + +- (void) miniaturize:(id) sender { + _callback(NSWindowMiniaturizeButton); +} + +- (void) zoom:(id) sender { + _callback(NSWindowZoomButton); +} + +@end // @implementation ButtonHandler + +namespace Platform { +namespace { + +[[nodiscard]] base::flat_map ButtonGeometries() { + auto result = base::flat_map(); + auto normal = QWidget(); + normal.hide(); + normal.createWinId(); + const auto view = reinterpret_cast(normal.winId()); + const auto window = [view window]; + const auto process = [&](NSWindowButton type) { + if (const auto button = [window standardWindowButton:type]) { + result.emplace(int(type), [button frame]); + } + }; + process(NSWindowCloseButton); + process(NSWindowMiniaturizeButton); + process(NSWindowZoomButton); + + const auto full = [window frame]; + const auto inner = [window contentRectForFrameRect:full].size.height; + const auto height = std::max(full.size.height - inner, 0.); + + result[int(NSWindowToolbarButton)] = { CGPoint(), CGSize{ full.size.width, height }}; + return result; +} + +} // namespace + +struct MacOverlayWidgetHelper::Data { + const not_null window; + const Fn maximize; + const base::flat_map buttons; + ButtonHandler *handler = nil; + NSWindow * __weak native = nil; + NSButton * __weak closeNative = nil; + NSButton * __weak miniaturizeNative = nil; + NSButton * __weak zoomNative = nil; + NSButton * __weak close = nil; + NSButton * __weak miniaturize = nil; + NSButton * __weak zoom = nil; +}; + +MacOverlayWidgetHelper::MacOverlayWidgetHelper( + not_null window, + Fn maximize) +: _data(std::make_unique(Data{ + .window = window, + .maximize = std::move(maximize), + .buttons = ButtonGeometries(), + .handler = [[ButtonHandler alloc] initWithCallback:[=](NSWindowButton button) { + activate(int(button)); + }] +})) { +} + +MacOverlayWidgetHelper::~MacOverlayWidgetHelper() { + [_data->handler release]; + _data->handler = nil; +} + +void MacOverlayWidgetHelper::activate(int button) { + const auto fullscreen = (_data->window->windowHandle()->flags() & Qt::FramelessWindowHint); + switch (NSWindowButton(button)) { + case NSWindowCloseButton: _data->window->close(); return; + case NSWindowMiniaturizeButton: [_data->native miniaturize:_data->handler]; return; + case NSWindowZoomButton: _data->maximize(!fullscreen); return; + } +} + +void MacOverlayWidgetHelper::beforeShow(bool fullscreen) { + _data->window->setAttribute(Qt::WA_MacAlwaysShowToolWindow, !fullscreen); + _data->window->windowHandle()->setFlag(Qt::FramelessWindowHint, fullscreen); + if (!fullscreen) { + _data->window->setGeometry({ 100, 100, 800, 600 }); + } + updateStyles(fullscreen); +} + +void MacOverlayWidgetHelper::afterShow(bool fullscreen) { + updateStyles(fullscreen); + refreshButtons(fullscreen); +} + +void MacOverlayWidgetHelper::resolveNative() { + if (const auto handle = _data->window->winId()) { + _data->native = [reinterpret_cast(handle) window]; + } +} + +void MacOverlayWidgetHelper::updateStyles(bool fullscreen) { + resolveNative(); + if (!_data->native) { + return; + } + + const auto window = _data->native; + const auto level = !fullscreen + ? NSNormalWindowLevel + : NSPopUpMenuWindowLevel; + [window setLevel:level]; + [window setHidesOnDeactivate:!_data->window->testAttribute(Qt::WA_MacAlwaysShowToolWindow)]; + [window setTitleVisibility:NSWindowTitleHidden]; + [window setTitlebarAppearsTransparent:YES]; + [window setStyleMask:[window styleMask] | NSWindowStyleMaskFullSizeContentView]; +} + +void MacOverlayWidgetHelper::refreshButtons(bool fullscreen) { + Expects(_data->native != nullptr); + + const auto window = _data->native; + auto next = CGPoint(); + const auto added = [&](NSRect frame) { + const auto left = frame.origin.x + frame.size.width * 1.5; + const auto top = frame.origin.y; + if (next.x < left) { + next.x = left; + } + next.y = top; + }; + for (const auto &[type, frame] : _data->buttons) { + added(frame); + } + const auto skip = fullscreen + ? _data->buttons.find(int(NSWindowToolbarButton))->second.size.height + : 0.; + const auto process = [&](auto native, auto custom, NSWindowButton type, auto action) { + auto retained = (NSButton*)nil; + while (const auto button = [window standardWindowButton:type]) { + if ([button superview] != [window contentView]) { + *native = button; + [button setHidden:YES]; + break; + } else if (button == *custom) { + retained = [button retain]; + } + [button removeFromSuperview]; + } + const auto i = _data->buttons.find(int(type)); + const auto frame = [&](NSButton *button) { + if (i != end(_data->buttons)) { + auto origin = i->second.origin; + origin.y += skip; + return NSRect{ origin, i->second.size }; + } + const auto size = [button frame].size; + auto result = NSRect{ next, size }; + added(result); + result.origin.y += skip; + return result; + }; + if (!retained) { + if (*custom) { + retained = *custom; + [retained retain]; + [retained removeFromSuperview]; + } else { + const auto style = NSWindowStyleMaskTitled + | NSWindowStyleMaskClosable + | NSWindowStyleMaskMiniaturizable + | NSWindowStyleMaskResizable; + *custom + = retained + = [NSWindow standardWindowButton:type forStyleMask:style]; + [retained setTarget:_data->handler]; + [retained setAction:action]; + [retained retain]; + } + } + [[window contentView] addSubview:retained]; + [retained setFrame:frame(retained)]; + [retained setEnabled:YES]; + [retained setHidden:NO]; + [retained release]; + }; + process( + &_data->closeNative, + &_data->close, + NSWindowCloseButton, + @selector(close:)); + process( + &_data->miniaturizeNative, + &_data->miniaturize, + NSWindowMiniaturizeButton, + @selector(miniaturize:)); + process( + &_data->zoomNative, + &_data->zoom, + NSWindowZoomButton, + @selector(zoom:)); +} + +void MacOverlayWidgetHelper::notifyFileDialogShown(bool shown) { + resolveNative(); + if (_data->native && !_data->window->isHidden()) { + const auto level = [_data->native level]; + if (level != NSNormalWindowLevel) { + const auto level = shown + ? NSModalPanelWindowLevel + : NSPopUpMenuWindowLevel; + [_data->native setLevel:level]; + } + } +} + +std::unique_ptr CreateOverlayWidgetHelper( + not_null window, + Fn maximize) { + return std::make_unique( + window, + std::move(maximize)); +} + +} // namespace Platform diff --git a/Telegram/SourceFiles/platform/platform_overlay_widget.cpp b/Telegram/SourceFiles/platform/platform_overlay_widget.cpp new file mode 100644 index 000000000..ae7f5c56c --- /dev/null +++ b/Telegram/SourceFiles/platform/platform_overlay_widget.cpp @@ -0,0 +1,34 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "platform/platform_overlay_widget.h" + +#include "ui/platform/ui_platform_window_title.h" +#include "styles/style_calls.h" + +namespace Platform { + +DefaultOverlayWidgetHelper::DefaultOverlayWidgetHelper( + not_null window, + Fn maximize) +: _controls(Ui::Platform::SetupSeparateTitleControls( + window, + st::callTitle, + std::move(maximize))) { +} + +DefaultOverlayWidgetHelper::~DefaultOverlayWidgetHelper() = default; + +void DefaultOverlayWidgetHelper::orderWidgets() { + _controls->wrap.raise(); +} + +bool DefaultOverlayWidgetHelper::skipTitleHitTest(QPoint position) { + return _controls->controls.geometry().contains(position); +} + +} // namespace Platform diff --git a/Telegram/SourceFiles/platform/platform_overlay_widget.h b/Telegram/SourceFiles/platform/platform_overlay_widget.h new file mode 100644 index 000000000..d87bf031f --- /dev/null +++ b/Telegram/SourceFiles/platform/platform_overlay_widget.h @@ -0,0 +1,66 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +namespace Ui { +class RpWindow; +} // namespace Ui + +namespace Ui::Platform { +struct SeparateTitleControls; +} // namespace Ui::Platform + +namespace Platform { + +class OverlayWidgetHelper { +public: + virtual ~OverlayWidgetHelper() = default; + + virtual void orderWidgets() { + } + [[nodiscard]] virtual bool skipTitleHitTest(QPoint position) { + return false; + } + virtual void beforeShow(bool fullscreen) { + } + virtual void afterShow(bool fullscreen) { + } + virtual void notifyFileDialogShown(bool shown) { + } +}; + +[[nodiscard]] std::unique_ptr CreateOverlayWidgetHelper( + not_null window, + Fn maximize); + +class DefaultOverlayWidgetHelper final : public OverlayWidgetHelper { +public: + DefaultOverlayWidgetHelper( + not_null window, + Fn maximize); + ~DefaultOverlayWidgetHelper(); + + void orderWidgets() override; + bool skipTitleHitTest(QPoint position) override; + +private: + const std::unique_ptr _controls; + +}; + +} // namespace Platform + +// Platform dependent implementations. + +#ifdef Q_OS_MAC +#include "platform/mac/overlay_widget_mac.h" +#elif defined Q_OS_UNIX // Q_OS_MAC +#include "platform/linux/overlay_widget_linux.h" +#elif defined Q_OS_WIN // Q_OS_MAC || Q_OS_UNIX +#include "platform/win/overlay_widget_win.h" +#endif // Q_OS_MAC || Q_OS_UNIX || Q_OS_WIN diff --git a/Telegram/SourceFiles/platform/win/overlay_widget_win.h b/Telegram/SourceFiles/platform/win/overlay_widget_win.h new file mode 100644 index 000000000..6430f4b20 --- /dev/null +++ b/Telegram/SourceFiles/platform/win/overlay_widget_win.h @@ -0,0 +1,20 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +namespace Platform { + +inline std::unique_ptr CreateOverlayWidgetHelper( + not_null window, + Fn maximize) { + return std::make_unique( + window, + std::move(maximize)); +} + +} // namespace Platform diff --git a/Telegram/build/prepare/prepare.py b/Telegram/build/prepare/prepare.py index c0c7dc3cf..1dbac6096 100644 --- a/Telegram/build/prepare/prepare.py +++ b/Telegram/build/prepare/prepare.py @@ -404,7 +404,7 @@ if customRunCommand: stage('patches', """ git clone https://github.com/desktop-app/patches.git cd patches - git checkout 720e1863f0 + git checkout d0fc458228 """) stage('msys64', """ diff --git a/Telegram/lib_ui b/Telegram/lib_ui index a56831e8d..c4838f589 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit a56831e8d00c58108a6b4ea6caf1c4e9cfe7f838 +Subproject commit c4838f589905fde2d43e3c3d37700523acf2ab9d