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

This commit is contained in:
allhaileris
2026-02-16 15:50:16 +03:00
commit afb81b8278
13816 changed files with 3689732 additions and 0 deletions

View 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

View 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

View 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

View 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

View 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

View 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