From 6735ee93dc6202d816fe1e3c06187007e2633565 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 13 Aug 2020 13:00:42 +0400 Subject: [PATCH] Support custom drag area for Ui::Window on macOS. --- ui/platform/mac/ui_window_mac.h | 2 + ui/platform/mac/ui_window_mac.mm | 90 ++++++++++++++++++++++++++++++ ui/platform/ui_platform_window.cpp | 8 ++- ui/platform/ui_platform_window.h | 2 + 4 files changed, 100 insertions(+), 2 deletions(-) diff --git a/ui/platform/mac/ui_window_mac.h b/ui/platform/mac/ui_window_mac.h index 85af0e9..3bb1970 100644 --- a/ui/platform/mac/ui_window_mac.h +++ b/ui/platform/mac/ui_window_mac.h @@ -29,6 +29,8 @@ private: class Private; friend class Private; + void setupBodyTitleAreaEvents() override; + void init(); void toggleCustomTitle(bool visible); diff --git a/ui/platform/mac/ui_window_mac.mm b/ui/platform/mac/ui_window_mac.mm index 8af0e0b..9ecdfdf 100644 --- a/ui/platform/mac/ui_window_mac.mm +++ b/ui/platform/mac/ui_window_mac.mm @@ -7,10 +7,13 @@ #include "ui/platform/mac/ui_window_mac.h" #include "ui/platform/mac/ui_window_title_mac.h" +#include "ui/widgets/window.h" #include "base/platform/base_platform_info.h" #include "styles/palette.h" #include +#include +#include #include #include @@ -78,6 +81,30 @@ private: }; +class EventFilter : public QObject, public QAbstractNativeEventFilter { +public: + EventFilter(not_null parent, Fn checkPerformDrag) + : QObject(parent) + , _checkPerformDrag(std::move(checkPerformDrag)) { + Expects(_checkPerformDrag != nullptr); + } + + bool nativeEventFilter( + const QByteArray &eventType, + void *message, + long *result) { + NSEvent *e = static_cast(message); + return (e && [e type] == NSEventTypeLeftMouseDown) + ? _checkPerformDrag([e window]) + : false; + return false; + } + +private: + Fn _checkPerformDrag; + +}; + } // namespace class WindowHelper::Private final { @@ -85,6 +112,9 @@ public: explicit Private(not_null owner); [[nodiscard]] int customTitleHeight() const; + [[nodiscard]] QRect controlsRect() const; + [[nodiscard]] bool checkNativeMove(void *nswindow) const; + void activateBeforeNativeMove(); private: void init(); @@ -117,6 +147,51 @@ int WindowHelper::Private::customTitleHeight() const { return _customTitleHeight; } +QRect WindowHelper::Private::controlsRect() const { + 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 { + if (_nativeWindow != nswindow + || ([_nativeWindow styleMask] & NSFullScreenWindowMask) == NSFullScreenWindowMask) { + return false; + } + const auto real = QPointF::fromCGPoint([NSEvent mouseLocation]); + const auto frame = QRectF::fromCGRect([_nativeWindow frame]); + const auto border = QMarginsF{ 3., 3., 3., 3. }; + return frame.marginsRemoved(border).contains(real); +} + +void WindowHelper::Private::activateBeforeNativeMove() { + [_nativeWindow makeKeyAndOrderFront:_nativeWindow]; +} + Fn WindowHelper::Private::toggleCustomTitleCallback() { return [=](bool visible) { _owner->toggleCustomTitle(visible); @@ -255,6 +330,21 @@ void WindowHelper::setGeometry(QRect rect) { rect.marginsAdded({ 0, (_title ? _title->height() : 0), 0, 0 })); } +void WindowHelper::setupBodyTitleAreaEvents() { + const auto controls = _private->controlsRect(); + qApp->installNativeEventFilter(new EventFilter(window(), [=](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::init() { rpl::combine( window()->sizeValue(), diff --git a/ui/platform/ui_platform_window.cpp b/ui/platform/ui_platform_window.cpp index a7ebaa5..4ee9ea0 100644 --- a/ui/platform/ui_platform_window.cpp +++ b/ui/platform/ui_platform_window.cpp @@ -57,6 +57,12 @@ void BasicWindowHelper::setBodyTitleArea( return; } _bodyTitleAreaTestMethod = std::move(testMethod); + setupBodyTitleAreaEvents(); +} + +void BasicWindowHelper::setupBodyTitleAreaEvents() { + // This is not done on macOS, because startSystemMove + // doesn't work from event handler there. body()->events() | rpl::start_with_next([=](not_null e) { const auto hitTest = [&] { return bodyTitleAreaHit( @@ -87,7 +93,6 @@ void BasicWindowHelper::setBodyTitleArea( _mousePressed = true; #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) || defined DESKTOP_APP_QT_PATCHED -#ifndef Q_OS_MAC // On macOS startSystemMove() doesn't work from here. } else if (e->type() == QEvent::MouseMove) { const auto mouseEvent = static_cast(e.get()); if (_mousePressed @@ -107,7 +112,6 @@ void BasicWindowHelper::setBodyTitleArea( _mousePressed = false; _window->windowHandle()->startSystemMove(); } -#endif // !Q_OS_MAC #endif // Qt >= 5.15 || DESKTOP_APP_QT_PATCHED } }, body()->lifetime()); diff --git a/ui/platform/ui_platform_window.h b/ui/platform/ui_platform_window.h index 8932b05..46de849 100644 --- a/ui/platform/ui_platform_window.h +++ b/ui/platform/ui_platform_window.h @@ -48,6 +48,8 @@ protected: } private: + virtual void setupBodyTitleAreaEvents(); + const not_null _window; Fn _bodyTitleAreaTestMethod; bool _mousePressed = false;