From 187df62e9da8eba79cc57febb7d141b8b2826d08 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 24 Sep 2019 18:44:12 +0300 Subject: [PATCH] Support nice Ui::Window under macOS. --- gyp/sources.txt | 4 + ui/platform/mac/ui_utility_mac.mm | 2 +- ui/platform/mac/ui_window_mac.h | 40 +++++ ui/platform/mac/ui_window_mac.mm | 234 +++++++++++++++++++++++++ ui/platform/mac/ui_window_title_mac.h | 45 +++++ ui/platform/mac/ui_window_title_mac.mm | 94 ++++++++++ ui/wrap/fade_wrap.cpp | 3 + 7 files changed, 421 insertions(+), 1 deletion(-) create mode 100644 ui/platform/mac/ui_window_mac.h create mode 100644 ui/platform/mac/ui_window_mac.mm create mode 100644 ui/platform/mac/ui_window_title_mac.h create mode 100644 ui/platform/mac/ui_window_title_mac.mm diff --git a/gyp/sources.txt b/gyp/sources.txt index 4ecd79a..859d8e4 100644 --- a/gyp/sources.txt +++ b/gyp/sources.txt @@ -30,6 +30,10 @@ <(src_loc)/ui/layers/layer_widget.h <(src_loc)/ui/platform/linux/ui_utility_linux.cpp <(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.mm <(src_loc)/ui/platform/win/ui_window_shadow_win.cpp diff --git a/ui/platform/mac/ui_utility_mac.mm b/ui/platform/mac/ui_utility_mac.mm index e0f06bf..4055c4a 100644 --- a/ui/platform/mac/ui_utility_mac.mm +++ b/ui/platform/mac/ui_utility_mac.mm @@ -4,7 +4,7 @@ // For license and copyright information please follow this link: // 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" diff --git a/ui/platform/mac/ui_window_mac.h b/ui/platform/mac/ui_window_mac.h new file mode 100644 index 0000000..0190360 --- /dev/null +++ b/ui/platform/mac/ui_window_mac.h @@ -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 window); + ~WindowHelper(); + + not_null 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 _window; + const std::unique_ptr _private; + const not_null _title; + const not_null _body; + +}; + +} // namespace Platform +} // namespace Ui diff --git a/ui/platform/mac/ui_window_mac.mm b/ui/platform/mac/ui_window_mac.mm new file mode 100644 index 0000000..3cc031e --- /dev/null +++ b/ui/platform/mac/ui_window_mac.mm @@ -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 +#include +#include + +@interface WindowObserver : NSObject { +} + +- (id) init:(Fn)toggleCustomTitleVisibility; +- (void) windowWillEnterFullScreen:(NSNotification *)aNotification; +- (void) windowWillExitFullScreen:(NSNotification *)aNotification; + +@end // @interface WindowObserver + +@implementation WindowObserver { + Fn _toggleCustomTitleVisibility; +} + +- (id) init:(Fn)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 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 _callback; + +}; + +} // namespace + +class WindowHelper::Private final { +public: + explicit Private(not_null owner); + + [[nodiscard]] int customTitleHeight() const; + +private: + void init(); + void initOpenGL(); + void resolveWeakPointers(); + void initCustomTitle(); + + [[nodiscard]] Fn toggleCustomTitleCallback(); + + const not_null _owner; + const WindowObserver *_observer = nullptr; + + NSWindow * __weak _nativeWindow = nil; + NSView * __weak _nativeView = nil; + + std::unique_ptr _layerCreationChecker; + + int _customTitleHeight = 0; + +}; + +WindowHelper::Private::Private(not_null owner) +: _owner(owner) +, _observer([[WindowObserver alloc] init:toggleCustomTitleCallback()]) { + init(); +} + +int WindowHelper::Private::customTitleHeight() const { + return _customTitleHeight; +} + +Fn WindowHelper::Private::toggleCustomTitleCallback() { + return [=](bool visible) { + _owner->toggleCustomTitle(visible); + }; +} + +void WindowHelper::Private::initOpenGL() { + auto forceOpenGL = std::make_unique(_owner->_window); +} + +void WindowHelper::Private::resolveWeakPointers() { + _owner->_window->createWinId(); + + _nativeView = reinterpret_cast(_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(_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 window) +: _window(window) +, _private(std::make_unique(this)) +, _title(_private->customTitleHeight() + ? Ui::CreateChild( + _window.get(), + _private->customTitleHeight()) + : nullptr) +, _body(Ui::CreateChild(_window.get())) { + init(); +} + +WindowHelper::~WindowHelper() { +} + +not_null 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 CreateWindowHelper( + not_null window) { + return std::make_unique(window); +} + +} // namespace Platform +} // namespace Ui diff --git a/ui/platform/mac/ui_window_title_mac.h b/ui/platform/mac/ui_window_title_mac.h new file mode 100644 index 0000000..7061ef2 --- /dev/null +++ b/ui/platform/mac/ui_window_title_mac.h @@ -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 +#include + +namespace Ui { + +class PlainShadow; + +namespace Platform { + +class TitleWidget : public RpWidget { +public: + TitleWidget(not_null 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 window() const; + + void init(int height); + + object_ptr _shadow; + QString _text; + QFont _font; + +}; + +} // namespace Platform +} // namespace Ui diff --git a/ui/platform/mac/ui_window_title_mac.mm b/ui/platform/mac/ui_window_title_mac.mm new file mode 100644 index 0000000..2f0720f --- /dev/null +++ b/ui/platform/mac/ui_window_title_mac.mm @@ -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 +#include +#include + +namespace Ui { +namespace Platform { + +TitleWidget::TitleWidget(not_null 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 TitleWidget::window() const { + return static_cast(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 diff --git a/ui/wrap/fade_wrap.cpp b/ui/wrap/fade_wrap.cpp index f37a1bd..92da08d 100644 --- a/ui/wrap/fade_wrap.cpp +++ b/ui/wrap/fade_wrap.cpp @@ -33,6 +33,9 @@ FadeWrap *FadeWrap::toggle( bool shown, anim::type animated) { auto changed = (shown != _animation.visible()); + if (!_duration) { + animated = anim::type::instant; + } if (shown) { if (animated == anim::type::normal) { if (!_animation.animating()) {