init
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
This commit is contained in:
41
Telegram/lib_ui/ui/platform/mac/ui_utility_mac.h
Normal file
41
Telegram/lib_ui/ui/platform/mac/ui_utility_mac.h
Normal file
@@ -0,0 +1,41 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "ui/platform/ui_platform_utility.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include <QtCore/QPoint>
|
||||
|
||||
namespace Ui {
|
||||
namespace Platform {
|
||||
|
||||
inline bool TranslucentWindowsSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
inline void ClearTransientParent(not_null<QWidget*> widget) {
|
||||
}
|
||||
|
||||
inline constexpr bool UseMainQueueGeneric() {
|
||||
return ::Platform::IsMacStoreBuild();
|
||||
}
|
||||
|
||||
inline bool WindowMarginsSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
inline void SetWindowMargins(not_null<QWidget*> widget, const QMargins &margins) {
|
||||
}
|
||||
|
||||
inline void ShowWindowMenu(not_null<QWidget*> widget, const QPoint &point) {
|
||||
}
|
||||
|
||||
inline void FixPopupMenuNativeEmojiPopup(not_null<PopupMenu*> menu) {
|
||||
}
|
||||
|
||||
} // namespace Platform
|
||||
} // namespace Ui
|
||||
168
Telegram/lib_ui/ui/platform/mac/ui_utility_mac.mm
Normal file
168
Telegram/lib_ui/ui/platform/mac/ui_utility_mac.mm
Normal file
@@ -0,0 +1,168 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include "ui/platform/mac/ui_utility_mac.h"
|
||||
|
||||
#include "ui/integration.h"
|
||||
|
||||
#include <QtGui/QPainter>
|
||||
#include <QtGui/QtEvents>
|
||||
#include <QtGui/QWindow>
|
||||
|
||||
#include <Cocoa/Cocoa.h>
|
||||
|
||||
#ifndef OS_MAC_STORE
|
||||
extern "C" {
|
||||
void _dispatch_main_queue_callback_4CF(mach_msg_header_t *msg);
|
||||
} // extern "C"
|
||||
#endif // OS_MAC_STORE
|
||||
|
||||
namespace Ui {
|
||||
namespace Platform {
|
||||
|
||||
bool IsApplicationActive() {
|
||||
return [[NSApplication sharedApplication] isActive];
|
||||
}
|
||||
|
||||
void InitOnTopPanel(not_null<QWidget*> panel) {
|
||||
Expects(!panel->windowHandle());
|
||||
|
||||
// Force creating windowHandle() without creating the platform window yet.
|
||||
panel->setAttribute(Qt::WA_NativeWindow, true);
|
||||
panel->windowHandle()->setProperty("_td_macNonactivatingPanelMask", QVariant(true));
|
||||
panel->setAttribute(Qt::WA_NativeWindow, false);
|
||||
|
||||
panel->createWinId();
|
||||
|
||||
auto platformWindow = [reinterpret_cast<NSView*>(panel->winId()) window];
|
||||
Assert([platformWindow isKindOfClass:[NSPanel class]]);
|
||||
|
||||
auto platformPanel = static_cast<NSPanel*>(platformWindow);
|
||||
[platformPanel setBackgroundColor:[NSColor clearColor]];
|
||||
[platformPanel setLevel:NSModalPanelWindowLevel];
|
||||
[platformPanel setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces|NSWindowCollectionBehaviorStationary|NSWindowCollectionBehaviorFullScreenAuxiliary|NSWindowCollectionBehaviorIgnoresCycle];
|
||||
[platformPanel setHidesOnDeactivate:NO];
|
||||
//[platformPanel setFloatingPanel:YES];
|
||||
|
||||
Integration::Instance().activationFromTopPanel();
|
||||
}
|
||||
|
||||
void DeInitOnTopPanel(not_null<QWidget*> panel) {
|
||||
auto platformWindow = [reinterpret_cast<NSView*>(panel->winId()) window];
|
||||
Assert([platformWindow isKindOfClass:[NSPanel class]]);
|
||||
|
||||
auto platformPanel = static_cast<NSPanel*>(platformWindow);
|
||||
auto newBehavior = ([platformPanel collectionBehavior] & (~NSWindowCollectionBehaviorCanJoinAllSpaces)) | NSWindowCollectionBehaviorMoveToActiveSpace;
|
||||
[platformPanel setCollectionBehavior:newBehavior];
|
||||
}
|
||||
|
||||
void ReInitOnTopPanel(not_null<QWidget*> panel) {
|
||||
auto platformWindow = [reinterpret_cast<NSView*>(panel->winId()) window];
|
||||
Assert([platformWindow isKindOfClass:[NSPanel class]]);
|
||||
|
||||
auto platformPanel = static_cast<NSPanel*>(platformWindow);
|
||||
auto newBehavior = ([platformPanel collectionBehavior] & (~NSWindowCollectionBehaviorMoveToActiveSpace)) | NSWindowCollectionBehaviorCanJoinAllSpaces;
|
||||
[platformPanel setCollectionBehavior:newBehavior];
|
||||
}
|
||||
|
||||
void ShowOverAll(not_null<QWidget*> widget, bool canFocus) {
|
||||
NSWindow *wnd = [reinterpret_cast<NSView*>(widget->winId()) window];
|
||||
[wnd setLevel:NSPopUpMenuWindowLevel];
|
||||
if (!canFocus) {
|
||||
[wnd setStyleMask:NSWindowStyleMaskUtilityWindow | NSWindowStyleMaskNonactivatingPanel];
|
||||
[wnd setCollectionBehavior:NSWindowCollectionBehaviorMoveToActiveSpace|NSWindowCollectionBehaviorStationary|NSWindowCollectionBehaviorFullScreenAuxiliary|NSWindowCollectionBehaviorIgnoresCycle];
|
||||
}
|
||||
}
|
||||
|
||||
void AcceptAllMouseInput(not_null<QWidget*> widget) {
|
||||
// https://github.com/telegramdesktop/tdesktop/issues/27025
|
||||
//
|
||||
// By default system clicks through fully transparent pixels,
|
||||
// and starting with macOS 14.1 it counts the transparency
|
||||
// incorrectly (as if `y` is mirrored), so when clicking
|
||||
// on a reactions strip outside of the menu column the click
|
||||
// is ignored and made on the underlying window, because at the
|
||||
// bottom of the menu in the same place there is nothing, empty.
|
||||
//
|
||||
// We explicitly request all the input to disable this behavior.
|
||||
//
|
||||
// See https://stackoverflow.com/a/29451199 and comments.
|
||||
NSWindow *window = [reinterpret_cast<NSView*>(widget->winId()) window];
|
||||
[window setIgnoresMouseEvents:NO];
|
||||
}
|
||||
|
||||
void DrainMainQueue() {
|
||||
#ifndef OS_MAC_STORE
|
||||
_dispatch_main_queue_callback_4CF(nullptr);
|
||||
#endif // OS_MAC_STORE
|
||||
}
|
||||
|
||||
void IgnoreAllActivation(not_null<QWidget*> widget) {
|
||||
}
|
||||
|
||||
void DisableSystemWindowResize(not_null<QWidget*> widget, QSize ratio) {
|
||||
const auto winId = widget->winId();
|
||||
if (const auto view = reinterpret_cast<NSView*>(winId)) {
|
||||
if (const auto window = [view window]) {
|
||||
window.styleMask &= ~NSWindowStyleMaskResizable;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<bool> IsOverlapped(
|
||||
not_null<QWidget*> widget,
|
||||
const QRect &rect) {
|
||||
NSWindow *window = [reinterpret_cast<NSView*>(widget->winId()) window];
|
||||
Assert(window != nullptr);
|
||||
|
||||
if (![window isOnActiveSpace]) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto nativeRect = QRect(
|
||||
widget->mapToGlobal(rect.topLeft()),
|
||||
rect.size()).toCGRect();
|
||||
|
||||
CGWindowID windowId = (CGWindowID)[window windowNumber];
|
||||
const CGWindowListOption options = kCGWindowListExcludeDesktopElements
|
||||
| kCGWindowListOptionOnScreenAboveWindow;
|
||||
CFArrayRef windows = CGWindowListCopyWindowInfo(options, windowId);
|
||||
if (!windows) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto guard = gsl::finally([&] {
|
||||
CFRelease(windows);
|
||||
});
|
||||
NSMutableArray *list = (__bridge NSMutableArray*)windows;
|
||||
for (NSDictionary *window in list) {
|
||||
NSNumber *alphaValue = [window objectForKey:@"kCGWindowAlpha"];
|
||||
const auto alpha = alphaValue ? [alphaValue doubleValue] : 1.;
|
||||
if (alpha == 0.) {
|
||||
continue;
|
||||
}
|
||||
NSString *owner = [window objectForKey:@"kCGWindowOwnerName"];
|
||||
NSNumber *layerValue = [window objectForKey:@"kCGWindowLayer"];
|
||||
const auto layer = layerValue ? [layerValue intValue] : 0;
|
||||
if (owner && [owner isEqualToString:@"Dock"] && layer == 20) {
|
||||
// It is always full screen.
|
||||
continue;
|
||||
}
|
||||
CFDictionaryRef bounds = (__bridge CFDictionaryRef)[window objectForKey:@"kCGWindowBounds"];
|
||||
if (!bounds) {
|
||||
continue;
|
||||
}
|
||||
CGRect rect;
|
||||
if (!CGRectMakeWithDictionaryRepresentation(bounds, &rect)) {
|
||||
continue;
|
||||
} else if (CGRectIntersectsRect(rect, nativeRect)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace Platform
|
||||
} // namespace Ui
|
||||
48
Telegram/lib_ui/ui/platform/mac/ui_window_mac.h
Normal file
48
Telegram/lib_ui/ui/platform/mac/ui_window_mac.h
Normal file
@@ -0,0 +1,48 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "ui/platform/ui_platform_window.h"
|
||||
|
||||
namespace Ui::Platform {
|
||||
|
||||
class TitleWidget;
|
||||
|
||||
class WindowHelper final : public BasicWindowHelper {
|
||||
public:
|
||||
explicit WindowHelper(not_null<RpWidget*> window);
|
||||
~WindowHelper();
|
||||
|
||||
not_null<RpWidget*> body() override;
|
||||
QMargins frameMargins() override;
|
||||
void setTitle(const QString &title) override;
|
||||
void setTitleStyle(const style::WindowTitle &st) override;
|
||||
void setMinimumSize(QSize size) override;
|
||||
void setFixedSize(QSize size) override;
|
||||
void setStaysOnTop(bool enabled) override;
|
||||
void setGeometry(QRect rect) override;
|
||||
void close() override;
|
||||
|
||||
const style::TextStyle &titleTextStyle() const override;
|
||||
|
||||
private:
|
||||
class Private;
|
||||
friend class Private;
|
||||
|
||||
void setupBodyTitleAreaEvents() override;
|
||||
|
||||
void init();
|
||||
void updateCustomTitleVisibility(bool force = false);
|
||||
|
||||
const std::unique_ptr<Private> _private;
|
||||
const not_null<TitleWidget*> _title;
|
||||
const not_null<RpWidget*> _body;
|
||||
bool _titleVisible = true;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Ui::Platform
|
||||
535
Telegram/lib_ui/ui/platform/mac/ui_window_mac.mm
Normal file
535
Telegram/lib_ui/ui/platform/mac/ui_window_mac.mm
Normal file
@@ -0,0 +1,535 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include "ui/platform/mac/ui_window_mac.h"
|
||||
|
||||
#include "ui/platform/mac/ui_window_title_mac.h"
|
||||
#include "ui/widgets/rp_window.h"
|
||||
#include "ui/qt_object_factory.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "base/qt/qt_common_adapters.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "styles/palette.h"
|
||||
|
||||
#include <QtCore/QCoreApplication>
|
||||
#include <QtCore/QAbstractNativeEventFilter>
|
||||
#include <QtGui/QWindow>
|
||||
#include <QtGui/QtEvents>
|
||||
#include <QOpenGLWidget>
|
||||
#include <Cocoa/Cocoa.h>
|
||||
|
||||
using FullScreenEvent = Ui::Platform::FullScreenEvent;
|
||||
|
||||
@interface WindowObserver : NSObject {
|
||||
}
|
||||
|
||||
- (id) initWithHandler:(Fn<void(FullScreenEvent)>)handler;
|
||||
- (void) windowWillEnterFullScreen:(NSNotification *)aNotification;
|
||||
- (void) windowWillExitFullScreen:(NSNotification *)aNotification;
|
||||
- (void) windowDidEnterFullScreen:(NSNotification *)aNotification;
|
||||
- (void) windowDidExitFullScreen:(NSNotification *)aNotification;
|
||||
|
||||
@end // @interface WindowObserver
|
||||
|
||||
@implementation WindowObserver {
|
||||
Fn<void(FullScreenEvent)> _handler;
|
||||
}
|
||||
|
||||
- (id) initWithHandler:(Fn<void(FullScreenEvent)>)handler {
|
||||
if (self = [super init]) {
|
||||
_handler = std::move(handler);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) windowWillEnterFullScreen:(NSNotification *)aNotification {
|
||||
_handler(FullScreenEvent::WillEnter);
|
||||
}
|
||||
|
||||
- (void) windowWillExitFullScreen:(NSNotification *)aNotification {
|
||||
_handler(FullScreenEvent::WillExit);
|
||||
}
|
||||
|
||||
- (void) windowDidEnterFullScreen:(NSNotification *)aNotification {
|
||||
_handler(FullScreenEvent::DidEnter);
|
||||
}
|
||||
|
||||
- (void) windowDidExitFullScreen:(NSNotification *)aNotification {
|
||||
_handler(FullScreenEvent::DidExit);
|
||||
}
|
||||
|
||||
@end // @implementation WindowObserver
|
||||
|
||||
namespace Ui::Platform {
|
||||
namespace {
|
||||
|
||||
class LayerCreationChecker : public QObject {
|
||||
public:
|
||||
LayerCreationChecker(NSView * __weak view, Fn<void()> callback)
|
||||
: _weakView(view)
|
||||
, _callback(std::move(callback)) {
|
||||
QCoreApplication::instance()->installEventFilter(this);
|
||||
}
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject *object, QEvent *event) override {
|
||||
if (!_weakView || [_weakView layer] != nullptr) {
|
||||
_callback();
|
||||
}
|
||||
return QObject::eventFilter(object, event);
|
||||
}
|
||||
|
||||
private:
|
||||
NSView * __weak _weakView = nil;
|
||||
Fn<void()> _callback;
|
||||
|
||||
};
|
||||
|
||||
class EventFilter : public QObject, public QAbstractNativeEventFilter {
|
||||
public:
|
||||
EventFilter(
|
||||
not_null<QObject*> parent,
|
||||
Fn<bool()> checkStartDrag,
|
||||
Fn<bool(void*)> checkPerformDrag)
|
||||
: QObject(parent)
|
||||
, _checkStartDrag(std::move(checkStartDrag))
|
||||
, _checkPerformDrag(std::move(checkPerformDrag)) {
|
||||
Expects(_checkPerformDrag != nullptr);
|
||||
Expects(_checkStartDrag != nullptr);
|
||||
}
|
||||
|
||||
bool nativeEventFilter(
|
||||
const QByteArray &eventType,
|
||||
void *message,
|
||||
native_event_filter_result *result) {
|
||||
if (NSEvent *e = static_cast<NSEvent*>(message)) {
|
||||
if ([e type] == NSEventTypeLeftMouseDown) {
|
||||
_dragStarted = _checkStartDrag();
|
||||
} else if (([e type] == NSEventTypeLeftMouseDragged)
|
||||
&& _dragStarted) {
|
||||
if (_checkPerformDrag([e window])) {
|
||||
return true;
|
||||
}
|
||||
_dragStarted = false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
bool _dragStarted = false;
|
||||
Fn<bool()> _checkStartDrag;
|
||||
Fn<bool(void*)> _checkPerformDrag;
|
||||
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
class WindowHelper::Private final {
|
||||
public:
|
||||
explicit Private(not_null<WindowHelper*> owner);
|
||||
~Private();
|
||||
|
||||
[[nodiscard]] int customTitleHeight() const;
|
||||
[[nodiscard]] QRect controlsRect() const;
|
||||
[[nodiscard]] bool checkNativeMove(void *nswindow) const;
|
||||
void activateBeforeNativeMove();
|
||||
void setStaysOnTop(bool enabled);
|
||||
void setNativeTitleVisibility(bool visible);
|
||||
void close();
|
||||
|
||||
private:
|
||||
void init();
|
||||
void initOpenGL();
|
||||
void resolveWeakPointers();
|
||||
void revalidateWeakPointers() const;
|
||||
void initCustomTitle();
|
||||
|
||||
[[nodiscard]] Fn<void(FullScreenEvent)> handleFullScreenEventCallback();
|
||||
void enforceStyle();
|
||||
|
||||
const not_null<WindowHelper*> _owner;
|
||||
const WindowObserver *_observer = nullptr;
|
||||
|
||||
NSWindow * __weak _nativeWindow = nil;
|
||||
NSView * __weak _nativeView = nil;
|
||||
bool _hadNativeValues = false;
|
||||
|
||||
std::unique_ptr<LayerCreationChecker> _layerCreationChecker;
|
||||
|
||||
int _customTitleHeight = 0;
|
||||
|
||||
};
|
||||
|
||||
WindowHelper::Private::Private(not_null<WindowHelper*> owner)
|
||||
: _owner(owner) {
|
||||
init();
|
||||
}
|
||||
|
||||
WindowHelper::Private::~Private() {
|
||||
if (_observer) {
|
||||
[_observer release];
|
||||
}
|
||||
}
|
||||
|
||||
int WindowHelper::Private::customTitleHeight() const {
|
||||
return _customTitleHeight;
|
||||
}
|
||||
|
||||
QRect WindowHelper::Private::controlsRect() const {
|
||||
revalidateWeakPointers();
|
||||
const auto button = [&](NSWindowButton type) {
|
||||
auto view = [_nativeWindow standardWindowButton:type];
|
||||
if (!view) {
|
||||
return QRect();
|
||||
}
|
||||
auto result = [view frame];
|
||||
for (auto parent = [view superview]; parent != nil; parent = [parent superview]) {
|
||||
const auto origin = [parent frame].origin;
|
||||
result.origin.x += origin.x;
|
||||
result.origin.y += origin.y;
|
||||
}
|
||||
return QRect(result.origin.x, result.origin.y, result.size.width, result.size.height);
|
||||
};
|
||||
auto result = QRect();
|
||||
const auto buttons = {
|
||||
NSWindowCloseButton,
|
||||
NSWindowMiniaturizeButton,
|
||||
NSWindowZoomButton,
|
||||
};
|
||||
for (const auto type : buttons) {
|
||||
result = result.united(button(type));
|
||||
}
|
||||
return QRect(
|
||||
result.x(),
|
||||
[_nativeWindow frame].size.height - result.y() - result.height(),
|
||||
result.width(),
|
||||
result.height());
|
||||
}
|
||||
|
||||
bool WindowHelper::Private::checkNativeMove(void *nswindow) const {
|
||||
revalidateWeakPointers();
|
||||
if (_nativeWindow != nswindow
|
||||
|| ([_nativeWindow styleMask] & NSWindowStyleMaskFullScreen) == NSWindowStyleMaskFullScreen) {
|
||||
return false;
|
||||
}
|
||||
const auto cgReal = [NSEvent mouseLocation];
|
||||
const auto real = QPointF(cgReal.x, cgReal.y);
|
||||
const auto cgFrame = [_nativeWindow frame];
|
||||
const auto frame = QRectF(cgFrame.origin.x, cgFrame.origin.y, cgFrame.size.width, cgFrame.size.height);
|
||||
const auto border = QMarginsF{ 3., 3., 3., 3. };
|
||||
return frame.marginsRemoved(border).contains(real);
|
||||
}
|
||||
|
||||
void WindowHelper::Private::activateBeforeNativeMove() {
|
||||
revalidateWeakPointers();
|
||||
[_nativeWindow makeKeyAndOrderFront:_nativeWindow];
|
||||
}
|
||||
|
||||
void WindowHelper::Private::setStaysOnTop(bool enabled) {
|
||||
_owner->BasicWindowHelper::setStaysOnTop(enabled);
|
||||
resolveWeakPointers();
|
||||
initCustomTitle();
|
||||
_owner->updateCustomTitleVisibility(true);
|
||||
}
|
||||
|
||||
void WindowHelper::Private::setNativeTitleVisibility(bool visible) {
|
||||
revalidateWeakPointers();
|
||||
if (!_nativeWindow) {
|
||||
return;
|
||||
}
|
||||
const auto value = visible ? NSWindowTitleVisible : NSWindowTitleHidden;
|
||||
[_nativeWindow setTitleVisibility:value];
|
||||
}
|
||||
|
||||
void WindowHelper::Private::close() {
|
||||
const auto weak = base::make_weak(_owner->window());
|
||||
QCloseEvent e;
|
||||
qApp->sendEvent(_owner->window(), &e);
|
||||
if (!e.isAccepted() || !weak) {
|
||||
return;
|
||||
}
|
||||
revalidateWeakPointers();
|
||||
if (_nativeWindow) {
|
||||
[_nativeWindow close];
|
||||
}
|
||||
}
|
||||
|
||||
Fn<void(FullScreenEvent)> WindowHelper::Private::handleFullScreenEventCallback() {
|
||||
return crl::guard(_owner->window(), [=](FullScreenEvent event) {
|
||||
switch (event) {
|
||||
case FullScreenEvent::WillEnter:
|
||||
_owner->_titleVisible = false;
|
||||
_owner->updateCustomTitleVisibility(true);
|
||||
break;
|
||||
case FullScreenEvent::WillExit:
|
||||
enforceStyle();
|
||||
_owner->_titleVisible = true;
|
||||
_owner->updateCustomTitleVisibility(true);
|
||||
break;
|
||||
case FullScreenEvent::DidEnter:
|
||||
break;
|
||||
case FullScreenEvent::DidExit:
|
||||
enforceStyle();
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void WindowHelper::Private::enforceStyle() {
|
||||
revalidateWeakPointers();
|
||||
if (_nativeWindow && _customTitleHeight > 0) {
|
||||
[_nativeWindow setStyleMask:[_nativeWindow styleMask] | NSWindowStyleMaskFullSizeContentView];
|
||||
}
|
||||
}
|
||||
|
||||
void WindowHelper::Private::initOpenGL() {
|
||||
//auto forceOpenGL = std::make_unique<QOpenGLWidget>(_owner->window());
|
||||
}
|
||||
|
||||
void WindowHelper::Private::resolveWeakPointers() {
|
||||
if (!_owner->window()->winId()) {
|
||||
_owner->window()->createWinId();
|
||||
}
|
||||
|
||||
_nativeView = reinterpret_cast<NSView*>(_owner->window()->winId());
|
||||
_nativeWindow = _nativeView ? [_nativeView window] : nullptr;
|
||||
_hadNativeValues = true;
|
||||
|
||||
Ensures(_nativeWindow != nullptr);
|
||||
}
|
||||
|
||||
void WindowHelper::Private::revalidateWeakPointers() const {
|
||||
if (_nativeWindow || !_hadNativeValues) {
|
||||
return;
|
||||
}
|
||||
const_cast<Private*>(this)->resolveWeakPointers();
|
||||
}
|
||||
|
||||
void WindowHelper::Private::initCustomTitle() {
|
||||
if (![_nativeWindow respondsToSelector:@selector(contentLayoutRect)]
|
||||
|| ![_nativeWindow respondsToSelector:@selector(setTitlebarAppearsTransparent:)]) {
|
||||
return;
|
||||
}
|
||||
|
||||
[_nativeWindow setTitlebarAppearsTransparent:YES];
|
||||
|
||||
if (_observer) {
|
||||
[_observer release];
|
||||
}
|
||||
_observer = [[WindowObserver alloc] initWithHandler:handleFullScreenEventCallback()];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:_observer selector:@selector(windowWillEnterFullScreen:) name:NSWindowWillEnterFullScreenNotification object:_nativeWindow];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:_observer selector:@selector(windowWillExitFullScreen:) name:NSWindowWillExitFullScreenNotification object:_nativeWindow];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:_observer selector:@selector(windowDidExitFullScreen:) name:NSWindowDidExitFullScreenNotification object:_nativeWindow];
|
||||
|
||||
// Qt has bug with layer-backed widgets containing QOpenGLWidgets.
|
||||
// See https://bugreports.qt.io/browse/QTBUG-64494
|
||||
// Emulate custom title instead (code below).
|
||||
//
|
||||
// Tried to backport a fix, testing.
|
||||
[_nativeWindow setStyleMask:[_nativeWindow styleMask] | NSWindowStyleMaskFullSizeContentView];
|
||||
auto inner = [_nativeWindow contentLayoutRect];
|
||||
auto full = [_nativeView frame];
|
||||
_customTitleHeight = qMax(qRound(full.size.height - inner.size.height), 0);
|
||||
|
||||
// Qt still has some bug with layer-backed widgets containing QOpenGLWidgets.
|
||||
// See https://github.com/telegramdesktop/tdesktop/issues/4150
|
||||
// Tried to workaround it by catching the first moment we have CALayer created
|
||||
// and explicitly setting contentsScale to window->backingScaleFactor there.
|
||||
_layerCreationChecker = std::make_unique<LayerCreationChecker>(_nativeView, [=] {
|
||||
if (_nativeView && _nativeWindow) {
|
||||
if (CALayer *layer = [_nativeView layer]) {
|
||||
[layer setContentsScale: [_nativeWindow backingScaleFactor]];
|
||||
_layerCreationChecker = nullptr;
|
||||
}
|
||||
} else {
|
||||
_layerCreationChecker = nullptr;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void WindowHelper::Private::init() {
|
||||
initOpenGL();
|
||||
resolveWeakPointers();
|
||||
initCustomTitle();
|
||||
}
|
||||
|
||||
WindowHelper::WindowHelper(not_null<RpWidget*> window)
|
||||
: BasicWindowHelper(window)
|
||||
, _private(std::make_unique<Private>(this))
|
||||
, _title(Ui::CreateChild<TitleWidget>(
|
||||
window.get(),
|
||||
_private->customTitleHeight()))
|
||||
, _body(Ui::CreateChild<RpWidget>(window.get())) {
|
||||
init();
|
||||
_title->setControlsRect(_private->controlsRect());
|
||||
}
|
||||
|
||||
WindowHelper::~WindowHelper() {
|
||||
}
|
||||
|
||||
not_null<RpWidget*> WindowHelper::body() {
|
||||
return _body;
|
||||
}
|
||||
|
||||
QMargins WindowHelper::frameMargins() {
|
||||
const auto titleHeight = !_title->isHidden() ? _title->height() : 0;
|
||||
return QMargins{ 0, titleHeight, 0, 0 };
|
||||
}
|
||||
|
||||
void WindowHelper::setTitle(const QString &title) {
|
||||
_title->setText(title);
|
||||
window()->setWindowTitle(title);
|
||||
}
|
||||
|
||||
void WindowHelper::setTitleStyle(const style::WindowTitle &st) {
|
||||
_title->setStyle(st);
|
||||
updateCustomTitleVisibility();
|
||||
}
|
||||
|
||||
void WindowHelper::updateCustomTitleVisibility(bool force) {
|
||||
const auto visible = !_title->shouldBeHidden() && _titleVisible;
|
||||
if (!force && _title->isHidden() != visible) {
|
||||
return;
|
||||
}
|
||||
_title->setVisible(visible);
|
||||
_private->setNativeTitleVisibility(!_titleVisible);
|
||||
}
|
||||
|
||||
void WindowHelper::setMinimumSize(QSize size) {
|
||||
window()->setMinimumSize(size.width(), frameMargins().top() + size.height());
|
||||
}
|
||||
|
||||
void WindowHelper::setFixedSize(QSize size) {
|
||||
window()->setFixedSize(size.width(), frameMargins().top() + size.height());
|
||||
}
|
||||
|
||||
void WindowHelper::setStaysOnTop(bool enabled) {
|
||||
_private->setStaysOnTop(enabled);
|
||||
}
|
||||
|
||||
void WindowHelper::setGeometry(QRect rect) {
|
||||
window()->setGeometry(rect.marginsAdded(frameMargins()));
|
||||
}
|
||||
|
||||
void WindowHelper::setupBodyTitleAreaEvents() {
|
||||
const auto controls = _private->controlsRect();
|
||||
qApp->installNativeEventFilter(new EventFilter(window(), [=] {
|
||||
const auto point = body()->mapFromGlobal(QCursor::pos());
|
||||
return (bodyTitleAreaHit(point) & WindowTitleHitTestFlag::Move);
|
||||
}, [=](void *nswindow) {
|
||||
const auto point = body()->mapFromGlobal(QCursor::pos());
|
||||
if (_private->checkNativeMove(nswindow)
|
||||
&& !controls.contains(point)
|
||||
&& (bodyTitleAreaHit(point) & WindowTitleHitTestFlag::Move)) {
|
||||
_private->activateBeforeNativeMove();
|
||||
window()->windowHandle()->startSystemMove();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}));
|
||||
}
|
||||
|
||||
void WindowHelper::close() {
|
||||
_private->close();
|
||||
}
|
||||
|
||||
const style::TextStyle &WindowHelper::titleTextStyle() const {
|
||||
return _title->textStyle();
|
||||
}
|
||||
|
||||
void WindowHelper::init() {
|
||||
updateCustomTitleVisibility(true);
|
||||
|
||||
style::PaletteChanged(
|
||||
) | rpl::on_next([=] {
|
||||
Ui::ForceFullRepaint(window());
|
||||
}, window()->lifetime());
|
||||
|
||||
rpl::combine(
|
||||
window()->sizeValue(),
|
||||
_title->heightValue(),
|
||||
_title->shownValue()
|
||||
) | rpl::on_next([=](QSize size, int titleHeight, bool shown) {
|
||||
if (!shown) {
|
||||
titleHeight = 0;
|
||||
}
|
||||
_body->setGeometry(
|
||||
0,
|
||||
titleHeight,
|
||||
size.width(),
|
||||
size.height() - titleHeight);
|
||||
}, _body->lifetime());
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
setBodyTitleArea([](QPoint widgetPoint) {
|
||||
using Flag = Ui::WindowTitleHitTestFlag;
|
||||
return (widgetPoint.y() < 0)
|
||||
? (Flag::Move | Flag::Maximize)
|
||||
: Flag::None;
|
||||
});
|
||||
#endif // Qt >= 6.0.0
|
||||
}
|
||||
|
||||
std::unique_ptr<BasicWindowHelper> CreateSpecialWindowHelper(
|
||||
not_null<RpWidget*> window) {
|
||||
return std::make_unique<WindowHelper>(window);
|
||||
}
|
||||
|
||||
bool NativeWindowFrameSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
rpl::producer<FullScreenEvent> FullScreenEvents(
|
||||
not_null<RpWidget*> window) {
|
||||
return [=](auto consumer) {
|
||||
auto result = rpl::lifetime();
|
||||
|
||||
struct State {
|
||||
~State() {
|
||||
if (observer) {
|
||||
[observer release];
|
||||
}
|
||||
}
|
||||
|
||||
WindowObserver *observer = nullptr;
|
||||
};
|
||||
const auto state = result.make_state<State>();
|
||||
|
||||
window->winIdValue() | rpl::on_next([=](WId winId) {
|
||||
if (const auto was = base::take(state->observer)) {
|
||||
[was release];
|
||||
}
|
||||
if (!winId) {
|
||||
return;
|
||||
}
|
||||
const auto view = reinterpret_cast<NSView*>(winId);
|
||||
const auto win = [view window];
|
||||
Ensures(win != nullptr);
|
||||
|
||||
const auto handler = [=](FullScreenEvent event) {
|
||||
consumer.put_next_copy(event);
|
||||
};
|
||||
state->observer = [[WindowObserver alloc] initWithHandler:handler];
|
||||
|
||||
const auto add = [&](NSNotificationName name, SEL selector) {
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
addObserver:state->observer
|
||||
selector:selector
|
||||
name:name
|
||||
object:win];
|
||||
};
|
||||
add(NSWindowWillEnterFullScreenNotification, @selector(windowWillEnterFullScreen:));
|
||||
add(NSWindowWillExitFullScreenNotification, @selector(windowWillExitFullScreen:));
|
||||
add(NSWindowDidEnterFullScreenNotification, @selector(windowDidEnterFullScreen:));
|
||||
add(NSWindowDidExitFullScreenNotification, @selector(windowDidExitFullScreen:));
|
||||
}, result);
|
||||
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace Ui::Platform
|
||||
59
Telegram/lib_ui/ui/platform/mac/ui_window_title_mac.h
Normal file
59
Telegram/lib_ui/ui/platform/mac/ui_window_title_mac.h
Normal file
@@ -0,0 +1,59 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "ui/text/text.h"
|
||||
#include "ui/rp_widget.h"
|
||||
#include "base/object_ptr.h"
|
||||
|
||||
#include <QtCore/QRect>
|
||||
#include <QtCore/QPoint>
|
||||
|
||||
namespace style {
|
||||
struct WindowTitle;
|
||||
struct TextStyle;
|
||||
} // namespace style
|
||||
|
||||
namespace Ui {
|
||||
|
||||
class PlainShadow;
|
||||
|
||||
namespace Platform {
|
||||
|
||||
class TitleWidget : public RpWidget {
|
||||
public:
|
||||
TitleWidget(not_null<RpWidget*> parent, int height);
|
||||
~TitleWidget();
|
||||
|
||||
void setText(const QString &text);
|
||||
void setStyle(const style::WindowTitle &st);
|
||||
void setControlsRect(const QRect &rect);
|
||||
[[nodiscard]] QString text() const;
|
||||
[[nodiscard]] bool shouldBeHidden() const;
|
||||
[[nodiscard]] const style::TextStyle &textStyle() const;
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
void mouseDoubleClickEvent(QMouseEvent *e) override;
|
||||
|
||||
private:
|
||||
not_null<RpWidget*> window() const;
|
||||
|
||||
void init(int height);
|
||||
|
||||
not_null<const style::WindowTitle*> _st;
|
||||
std::unique_ptr<style::TextStyle> _textStyle;
|
||||
object_ptr<Ui::PlainShadow> _shadow;
|
||||
QString _text;
|
||||
Ui::Text::String _string;
|
||||
int _controlsRight = 0;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Platform
|
||||
} // namespace Ui
|
||||
152
Telegram/lib_ui/ui/platform/mac/ui_window_title_mac.mm
Normal file
152
Telegram/lib_ui/ui/platform/mac/ui_window_title_mac.mm
Normal file
@@ -0,0 +1,152 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include "ui/platform/mac/ui_window_title_mac.h"
|
||||
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "ui/platform/ui_platform_window_title.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/shadow.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "base/debug_log.h"
|
||||
#include "styles/style_widgets.h"
|
||||
#include "styles/palette.h"
|
||||
|
||||
#include <QtGui/QPainter>
|
||||
#include <QtGui/QtEvents>
|
||||
#include <QtGui/QWindow>
|
||||
|
||||
namespace Ui {
|
||||
namespace Platform {
|
||||
|
||||
std::shared_ptr<TitleControlsLayout> TitleControlsLayout::Create() {
|
||||
return std::shared_ptr<TitleControlsLayout>(new TitleControlsLayout({
|
||||
.left = {
|
||||
TitleControls::Control::Close,
|
||||
TitleControls::Control::Minimize,
|
||||
TitleControls::Control::Maximize,
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
TitleWidget::TitleWidget(not_null<RpWidget*> parent, int height)
|
||||
: RpWidget(parent)
|
||||
, _st(&st::defaultWindowTitle)
|
||||
, _shadow(this, st::titleShadow) {
|
||||
init(height);
|
||||
}
|
||||
|
||||
TitleWidget::~TitleWidget() = default;
|
||||
|
||||
void TitleWidget::setText(const QString &text) {
|
||||
if (_text != text) {
|
||||
_text = text;
|
||||
_string.setText(textStyle(), text);
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
void TitleWidget::setStyle(const style::WindowTitle &st) {
|
||||
_st = &st;
|
||||
update();
|
||||
}
|
||||
|
||||
void TitleWidget::setControlsRect(const QRect &rect) {
|
||||
_controlsRight = rect.left() * 2 + rect.width();
|
||||
}
|
||||
|
||||
bool TitleWidget::shouldBeHidden() const {
|
||||
return !_st->height;
|
||||
}
|
||||
|
||||
const style::TextStyle &TitleWidget::textStyle() const {
|
||||
return *_textStyle;
|
||||
}
|
||||
|
||||
QString TitleWidget::text() const {
|
||||
return _text;
|
||||
}
|
||||
|
||||
not_null<RpWidget*> TitleWidget::window() const {
|
||||
return static_cast<RpWidget*>(parentWidget());
|
||||
}
|
||||
|
||||
void TitleWidget::init(int height) {
|
||||
setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
|
||||
window()->widthValue(
|
||||
) | rpl::on_next([=](int width) {
|
||||
setGeometry(0, 0, width, height);
|
||||
}, lifetime());
|
||||
|
||||
const auto setFromFont = [&](const style::font &font) {
|
||||
_textStyle = std::make_unique<style::TextStyle>(style::TextStyle{
|
||||
.font = font,
|
||||
});
|
||||
};
|
||||
|
||||
const auto families = QStringList{
|
||||
u".AppleSystemUIFont"_q,
|
||||
u".SF NS Text"_q,
|
||||
u"Helvetica Neue"_q,
|
||||
};
|
||||
for (auto family : families) {
|
||||
auto font = QFont();
|
||||
font.setFamily(family);
|
||||
if (QFontInfo(font).family() == font.family()) {
|
||||
static const auto logged = [&] {
|
||||
LOG(("Title Font: %1").arg(family));
|
||||
return true;
|
||||
}();
|
||||
const auto apple = (family == u".AppleSystemUIFont"_q);
|
||||
setFromFont(style::font(
|
||||
apple ? 13 : (height * 15) / 24,
|
||||
apple ? style::FontFlag::Bold : style::FontFlag(),
|
||||
family));
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!_textStyle) {
|
||||
setFromFont(style::font(13, style::FontFlag::Semibold, 0));
|
||||
}
|
||||
}
|
||||
|
||||
void TitleWidget::paintEvent(QPaintEvent *e) {
|
||||
Painter p(this);
|
||||
|
||||
const auto active = isActiveWindow();
|
||||
p.fillRect(rect(), active ? _st->bgActive : _st->bg);
|
||||
|
||||
p.setPen(active ? _st->fgActive : _st->fg);
|
||||
|
||||
const auto full = _string.maxWidth();
|
||||
const auto top = (height() - _textStyle->font->height) / 2;
|
||||
if (::Platform::IsMac26_0OrGreater()
|
||||
|| ((width() - _controlsRight * 2) < full)) {
|
||||
const auto left = _controlsRight;
|
||||
_string.drawElided(p, left, top, width() - left);
|
||||
} else {
|
||||
const auto left = (width() - full) / 2;
|
||||
_string.draw(p, left, top, full);
|
||||
}
|
||||
}
|
||||
|
||||
void TitleWidget::resizeEvent(QResizeEvent *e) {
|
||||
_shadow->setGeometry(0, height() - st::lineWidth, width(), st::lineWidth);
|
||||
}
|
||||
|
||||
void TitleWidget::mouseDoubleClickEvent(QMouseEvent *e) {
|
||||
const auto window = parentWidget();
|
||||
if (window->windowState() == Qt::WindowMaximized) {
|
||||
window->setWindowState(Qt::WindowNoState);
|
||||
} else {
|
||||
window->setWindowState(Qt::WindowMaximized);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Platform
|
||||
} // namespace Ui
|
||||
Reference in New Issue
Block a user