Initial implementation of windowed media viewer on macOS.

This commit is contained in:
John Preston 2023-02-14 17:27:52 +04:00
parent 7a5c9a6fb8
commit 2a1e3c4453
11 changed files with 475 additions and 41 deletions

View File

@ -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

View File

@ -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<Ui::GL::Window>())
, _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();

View File

@ -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<Ui::GL::Window> _wrap;
const not_null<Ui::RpWindow*> _window;
#ifndef Q_OS_MAC
const std::unique_ptr<Ui::Platform::SeparateTitleControls> _controls;
#endif
const std::unique_ptr<Platform::OverlayWidgetHelper> _helper;
const not_null<Ui::RpWidget*> _body;
const std::unique_ptr<Ui::RpWidgetWrap> _surface;
const not_null<QWidget*> _widget;

View File

@ -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<OverlayWidgetHelper> CreateOverlayWidgetHelper(
not_null<Ui::RpWindow*> window,
Fn<void(bool)> maximize) {
return std::make_unique<DefaultOverlayWidgetHelper>(
window,
std::move(maximize));
}
} // namespace Platform

View File

@ -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<Ui::RpWindow*> window,
Fn<void(bool)> 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> _data;
};
} // namespace Platform

View File

@ -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 <QtGui/QWindow>
#include <Cocoa/Cocoa.h>
@interface ButtonHandler : NSObject {
}
- (instancetype) initWithCallback:(Fn<void(NSWindowButton)>)callback;
- (void) close:(id) sender;
- (void) miniaturize:(id) sender;
- (void) zoom:(id) sender;
@end // @interface ButtonHandler
@implementation ButtonHandler {
Fn<void(NSWindowButton)> _callback;
}
- (instancetype) initWithCallback:(Fn<void(NSWindowButton)>)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<int, NSRect> ButtonGeometries() {
auto result = base::flat_map<int, NSRect>();
auto normal = QWidget();
normal.hide();
normal.createWinId();
const auto view = reinterpret_cast<NSView*>(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<Ui::RpWindow*> window;
const Fn<void(bool)> maximize;
const base::flat_map<int, NSRect> 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<Ui::RpWindow*> window,
Fn<void(bool)> maximize)
: _data(std::make_unique<Data>(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<NSView*>(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<OverlayWidgetHelper> CreateOverlayWidgetHelper(
not_null<Ui::RpWindow*> window,
Fn<void(bool)> maximize) {
return std::make_unique<MacOverlayWidgetHelper>(
window,
std::move(maximize));
}
} // namespace Platform

View File

@ -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<Ui::RpWindow*> window,
Fn<void(bool)> 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

View File

@ -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<OverlayWidgetHelper> CreateOverlayWidgetHelper(
not_null<Ui::RpWindow*> window,
Fn<void(bool)> maximize);
class DefaultOverlayWidgetHelper final : public OverlayWidgetHelper {
public:
DefaultOverlayWidgetHelper(
not_null<Ui::RpWindow*> window,
Fn<void(bool)> maximize);
~DefaultOverlayWidgetHelper();
void orderWidgets() override;
bool skipTitleHitTest(QPoint position) override;
private:
const std::unique_ptr<Ui::Platform::SeparateTitleControls> _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

View File

@ -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<OverlayWidgetHelper> CreateOverlayWidgetHelper(
not_null<Ui::RpWindow*> window,
Fn<void(bool)> maximize) {
return std::make_unique<DefaultOverlayWidgetHelper>(
window,
std::move(maximize));
}
} // namespace Platform

View File

@ -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', """

@ -1 +1 @@
Subproject commit a56831e8d00c58108a6b4ea6caf1c4e9cfe7f838
Subproject commit c4838f589905fde2d43e3c3d37700523acf2ab9d