Support nice Ui::Window under macOS.
This commit is contained in:
parent
d5c9ad77a9
commit
187df62e9d
7 changed files with 421 additions and 1 deletions
|
|
@ -30,6 +30,10 @@
|
||||||
<(src_loc)/ui/layers/layer_widget.h
|
<(src_loc)/ui/layers/layer_widget.h
|
||||||
<(src_loc)/ui/platform/linux/ui_utility_linux.cpp
|
<(src_loc)/ui/platform/linux/ui_utility_linux.cpp
|
||||||
<(src_loc)/ui/platform/linux/ui_utility_linux.h
|
<(src_loc)/ui/platform/linux/ui_utility_linux.h
|
||||||
|
<(src_loc)/ui/platform/mac/ui_window_mac.h
|
||||||
|
<(src_loc)/ui/platform/mac/ui_window_mac.mm
|
||||||
|
<(src_loc)/ui/platform/mac/ui_window_title_mac.h
|
||||||
|
<(src_loc)/ui/platform/mac/ui_window_title_mac.mm
|
||||||
<(src_loc)/ui/platform/mac/ui_utility_mac.h
|
<(src_loc)/ui/platform/mac/ui_utility_mac.h
|
||||||
<(src_loc)/ui/platform/mac/ui_utility_mac.mm
|
<(src_loc)/ui/platform/mac/ui_utility_mac.mm
|
||||||
<(src_loc)/ui/platform/win/ui_window_shadow_win.cpp
|
<(src_loc)/ui/platform/win/ui_window_shadow_win.cpp
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
// For license and copyright information please follow this link:
|
// For license and copyright information please follow this link:
|
||||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||||
//
|
//
|
||||||
#include "ui/platform/mac/ui_platform_mac.h"
|
#include "ui/platform/mac/ui_utility_mac.h"
|
||||||
|
|
||||||
#include "ui/integration.h"
|
#include "ui/integration.h"
|
||||||
|
|
||||||
|
|
|
||||||
40
ui/platform/mac/ui_window_mac.h
Normal file
40
ui/platform/mac/ui_window_mac.h
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
// 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 {
|
||||||
|
namespace Platform {
|
||||||
|
|
||||||
|
class TitleWidget;
|
||||||
|
|
||||||
|
class WindowHelper final : public BasicWindowHelper {
|
||||||
|
public:
|
||||||
|
explicit WindowHelper(not_null<RpWidget*> window);
|
||||||
|
~WindowHelper();
|
||||||
|
|
||||||
|
not_null<RpWidget*> body() override;
|
||||||
|
void setTitle(const QString &title) override;
|
||||||
|
void setSizeMin(QSize size) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
class Private;
|
||||||
|
friend class Private;
|
||||||
|
|
||||||
|
void init();
|
||||||
|
void toggleCustomTitle(bool visible);
|
||||||
|
|
||||||
|
const not_null<RpWidget*> _window;
|
||||||
|
const std::unique_ptr<Private> _private;
|
||||||
|
const not_null<TitleWidget*> _title;
|
||||||
|
const not_null<RpWidget*> _body;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Platform
|
||||||
|
} // namespace Ui
|
||||||
234
ui/platform/mac/ui_window_mac.mm
Normal file
234
ui/platform/mac/ui_window_mac.mm
Normal file
|
|
@ -0,0 +1,234 @@
|
||||||
|
// 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 "base/platform/base_platform_info.h"
|
||||||
|
#include "styles/palette.h"
|
||||||
|
|
||||||
|
#include <QtCore/QCoreApplication>
|
||||||
|
#include <QtWidgets/QOpenGLWidget>
|
||||||
|
#include <Cocoa/Cocoa.h>
|
||||||
|
|
||||||
|
@interface WindowObserver : NSObject {
|
||||||
|
}
|
||||||
|
|
||||||
|
- (id) init:(Fn<void(bool)>)toggleCustomTitleVisibility;
|
||||||
|
- (void) windowWillEnterFullScreen:(NSNotification *)aNotification;
|
||||||
|
- (void) windowWillExitFullScreen:(NSNotification *)aNotification;
|
||||||
|
|
||||||
|
@end // @interface WindowObserver
|
||||||
|
|
||||||
|
@implementation WindowObserver {
|
||||||
|
Fn<void(bool)> _toggleCustomTitleVisibility;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (id) init:(Fn<void(bool)>)toggleCustomTitleVisibility {
|
||||||
|
if (self = [super init]) {
|
||||||
|
_toggleCustomTitleVisibility = toggleCustomTitleVisibility;
|
||||||
|
}
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void) windowWillEnterFullScreen:(NSNotification *)aNotification {
|
||||||
|
_toggleCustomTitleVisibility(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void) windowWillExitFullScreen:(NSNotification *)aNotification {
|
||||||
|
_toggleCustomTitleVisibility(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@end // @implementation MainWindowObserver
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
namespace 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;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
class WindowHelper::Private final {
|
||||||
|
public:
|
||||||
|
explicit Private(not_null<WindowHelper*> owner);
|
||||||
|
|
||||||
|
[[nodiscard]] int customTitleHeight() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void init();
|
||||||
|
void initOpenGL();
|
||||||
|
void resolveWeakPointers();
|
||||||
|
void initCustomTitle();
|
||||||
|
|
||||||
|
[[nodiscard]] Fn<void(bool)> toggleCustomTitleCallback();
|
||||||
|
|
||||||
|
const not_null<WindowHelper*> _owner;
|
||||||
|
const WindowObserver *_observer = nullptr;
|
||||||
|
|
||||||
|
NSWindow * __weak _nativeWindow = nil;
|
||||||
|
NSView * __weak _nativeView = nil;
|
||||||
|
|
||||||
|
std::unique_ptr<LayerCreationChecker> _layerCreationChecker;
|
||||||
|
|
||||||
|
int _customTitleHeight = 0;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
WindowHelper::Private::Private(not_null<WindowHelper*> owner)
|
||||||
|
: _owner(owner)
|
||||||
|
, _observer([[WindowObserver alloc] init:toggleCustomTitleCallback()]) {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
int WindowHelper::Private::customTitleHeight() const {
|
||||||
|
return _customTitleHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
Fn<void(bool)> WindowHelper::Private::toggleCustomTitleCallback() {
|
||||||
|
return [=](bool visible) {
|
||||||
|
_owner->toggleCustomTitle(visible);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowHelper::Private::initOpenGL() {
|
||||||
|
auto forceOpenGL = std::make_unique<QOpenGLWidget>(_owner->_window);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowHelper::Private::resolveWeakPointers() {
|
||||||
|
_owner->_window->createWinId();
|
||||||
|
|
||||||
|
_nativeView = reinterpret_cast<NSView*>(_owner->_window->winId());
|
||||||
|
_nativeWindow = _nativeView ? [_nativeView window] : nullptr;
|
||||||
|
|
||||||
|
Ensures(_nativeWindow != nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowHelper::Private::initCustomTitle() {
|
||||||
|
if (![_nativeWindow respondsToSelector:@selector(contentLayoutRect)]
|
||||||
|
|| ![_nativeWindow respondsToSelector:@selector(setTitlebarAppearsTransparent:)]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
[_nativeWindow setTitlebarAppearsTransparent:YES];
|
||||||
|
|
||||||
|
[[NSNotificationCenter defaultCenter] addObserver:_observer selector:@selector(windowWillEnterFullScreen:) name:NSWindowWillEnterFullScreenNotification object:_nativeWindow];
|
||||||
|
[[NSNotificationCenter defaultCenter] addObserver:_observer selector:@selector(windowWillExitFullScreen:) name:NSWindowWillExitFullScreenNotification 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] | NSFullSizeContentViewWindowMask];
|
||||||
|
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)
|
||||||
|
: _window(window)
|
||||||
|
, _private(std::make_unique<Private>(this))
|
||||||
|
, _title(_private->customTitleHeight()
|
||||||
|
? Ui::CreateChild<TitleWidget>(
|
||||||
|
_window.get(),
|
||||||
|
_private->customTitleHeight())
|
||||||
|
: nullptr)
|
||||||
|
, _body(Ui::CreateChild<RpWidget>(_window.get())) {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
WindowHelper::~WindowHelper() {
|
||||||
|
}
|
||||||
|
|
||||||
|
not_null<RpWidget*> WindowHelper::body() {
|
||||||
|
return _body;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowHelper::setTitle(const QString &title) {
|
||||||
|
if (_title) {
|
||||||
|
_title->setText(title);
|
||||||
|
}
|
||||||
|
_window->setWindowTitle(
|
||||||
|
(!_title || _title->isHidden()) ? title : QString());
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowHelper::toggleCustomTitle(bool visible) {
|
||||||
|
if (!_title || _title->isHidden() != visible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_title->setVisible(visible);
|
||||||
|
_window->setWindowTitle(visible ? QString() : _title->text());
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowHelper::setSizeMin(QSize size) {
|
||||||
|
_window->setMinimumSize(size.width(), _title->height() + size.height());
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowHelper::init() {
|
||||||
|
rpl::combine(
|
||||||
|
_window->sizeValue(),
|
||||||
|
_title->heightValue(),
|
||||||
|
_title->shownValue()
|
||||||
|
) | rpl::start_with_next([=](QSize size, int titleHeight, bool shown) {
|
||||||
|
if (!shown) {
|
||||||
|
titleHeight = 0;
|
||||||
|
}
|
||||||
|
_body->setGeometry(
|
||||||
|
0,
|
||||||
|
titleHeight,
|
||||||
|
size.width(),
|
||||||
|
size.height() - titleHeight);
|
||||||
|
}, _body->lifetime());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<BasicWindowHelper> CreateWindowHelper(
|
||||||
|
not_null<RpWidget*> window) {
|
||||||
|
return std::make_unique<WindowHelper>(window);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Platform
|
||||||
|
} // namespace Ui
|
||||||
45
ui/platform/mac/ui_window_title_mac.h
Normal file
45
ui/platform/mac/ui_window_title_mac.h
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
// 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/rp_widget.h"
|
||||||
|
#include "base/object_ptr.h"
|
||||||
|
|
||||||
|
#include <QtCore/QRect>
|
||||||
|
#include <QtCore/QPoint>
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
|
||||||
|
class PlainShadow;
|
||||||
|
|
||||||
|
namespace Platform {
|
||||||
|
|
||||||
|
class TitleWidget : public RpWidget {
|
||||||
|
public:
|
||||||
|
TitleWidget(not_null<RpWidget*> parent, int height);
|
||||||
|
|
||||||
|
void setText(const QString &text);
|
||||||
|
[[nodiscard]] QString text() 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);
|
||||||
|
|
||||||
|
object_ptr<Ui::PlainShadow> _shadow;
|
||||||
|
QString _text;
|
||||||
|
QFont _font;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Platform
|
||||||
|
} // namespace Ui
|
||||||
94
ui/platform/mac/ui_window_title_mac.mm
Normal file
94
ui/platform/mac/ui_window_title_mac.mm
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
// 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 "ui/widgets/buttons.h"
|
||||||
|
#include "ui/widgets/shadow.h"
|
||||||
|
#include "ui/ui_utility.h"
|
||||||
|
#include "styles/style_widgets.h"
|
||||||
|
#include "styles/palette.h"
|
||||||
|
|
||||||
|
#include <QtGui/QPainter>
|
||||||
|
#include <QtGui/QtEvents>
|
||||||
|
#include <QtGui/QWindow>
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
namespace Platform {
|
||||||
|
|
||||||
|
TitleWidget::TitleWidget(not_null<RpWidget*> parent, int height)
|
||||||
|
: RpWidget(parent)
|
||||||
|
, _shadow(this, st::titleShadow) {
|
||||||
|
init(height);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TitleWidget::setText(const QString &text) {
|
||||||
|
if (_text != text) {
|
||||||
|
_text = text;
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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::start_with_next([=](int width) {
|
||||||
|
setGeometry(0, 0, width, st::titleHeight);
|
||||||
|
}, lifetime());
|
||||||
|
|
||||||
|
const auto families = QStringList{
|
||||||
|
QString(".SF NS Text"),
|
||||||
|
QString("Helvetica Neue")
|
||||||
|
};
|
||||||
|
for (auto family : families) {
|
||||||
|
_font.setFamily(family);
|
||||||
|
if (QFontInfo(_font).family() == _font.family()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (QFontInfo(_font).family() == _font.family()) {
|
||||||
|
_font.setPixelSize((height * 15) / 24);
|
||||||
|
} else {
|
||||||
|
_font = st::normalFont;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TitleWidget::paintEvent(QPaintEvent *e) {
|
||||||
|
QPainter p(this);
|
||||||
|
|
||||||
|
const auto active = isActiveWindow();
|
||||||
|
p.fillRect(rect(), active ? st::titleBgActive : st::titleBg);
|
||||||
|
|
||||||
|
p.setFont(_font);
|
||||||
|
p.setPen(active ? st::titleFgActive : st::titleFg);
|
||||||
|
p.drawText(rect(), _text, style::al_center);
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
@ -33,6 +33,9 @@ FadeWrap<RpWidget> *FadeWrap<RpWidget>::toggle(
|
||||||
bool shown,
|
bool shown,
|
||||||
anim::type animated) {
|
anim::type animated) {
|
||||||
auto changed = (shown != _animation.visible());
|
auto changed = (shown != _animation.visible());
|
||||||
|
if (!_duration) {
|
||||||
|
animated = anim::type::instant;
|
||||||
|
}
|
||||||
if (shown) {
|
if (shown) {
|
||||||
if (animated == anim::type::normal) {
|
if (animated == anim::type::normal) {
|
||||||
if (!_animation.animating()) {
|
if (!_animation.animating()) {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue