From c9235ec9c25565516da04abf083e9c0500de58bc Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 7 Aug 2020 19:38:27 +0400 Subject: [PATCH 01/14] Add label to call button. --- ui/widgets/widgets.style | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/widgets/widgets.style b/ui/widgets/widgets.style index b39af53..443b830 100644 --- a/ui/widgets/widgets.style +++ b/ui/widgets/widgets.style @@ -382,6 +382,7 @@ CallButton { angle: double; outerRadius: pixels; outerBg: color; + label: FlatLabel; } Menu { From f7bcb15bad3879b448a9b5bd3f9827009151b2f3 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 7 Aug 2020 23:35:32 +0400 Subject: [PATCH 02/14] Convert library to STATIC. --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 562ba9c..15d69e0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ # For license and copyright information please follow this link: # https://github.com/desktop-app/legal/blob/master/LEGAL -add_library(lib_ui OBJECT) +add_library(lib_ui STATIC) add_library(desktop-app::lib_ui ALIAS lib_ui) init_target(lib_ui) From 8a35ac5d2dc3c6e5aeb224c9fd09075ddedfe456 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 12 Aug 2020 16:23:57 +0400 Subject: [PATCH 03/14] Add opaque call popup background color. --- ui/colors.palette | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ui/colors.palette b/ui/colors.palette index 4516f3d..6231b0f 100644 --- a/ui/colors.palette +++ b/ui/colors.palette @@ -536,7 +536,8 @@ mediaviewTransparentFg: #cccccc; // another transparent filling part notificationBg: windowBg; // custom notification window background // calls -callBg: #26282cf2; // phone call popup background +callBg: #26282cf2; // old phone call popup background +callBgOpaque: #1b1f23 | callBg; // phone call popup background callNameFg: #ffffff; // phone call popup name text callFingerprintBg: #00000066; // phone call popup emoji fingerprint background callStatusFg: #aaabac; // phone call popup status text From 608b25bd32d01e233df0d78ccb5fd97028a82ae9 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 12 Aug 2020 17:33:26 +0400 Subject: [PATCH 04/14] Allow custom drag area for Ui::Window. --- CMakeLists.txt | 1 + ui/platform/linux/ui_window_linux.cpp | 2 +- ui/platform/mac/ui_window_mac.h | 1 - ui/platform/mac/ui_window_mac.mm | 26 ++++----- ui/platform/ui_platform_window.cpp | 77 +++++++++++++++++++++++++++ ui/platform/ui_platform_window.h | 41 +++++++++++--- ui/platform/win/ui_window_win.cpp | 64 +++++++++++----------- ui/platform/win/ui_window_win.h | 5 +- ui/widgets/window.cpp | 38 +++++-------- ui/widgets/window.h | 1 + 10 files changed, 177 insertions(+), 79 deletions(-) create mode 100644 ui/platform/ui_platform_window.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 15d69e0..ecf3f2b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -84,6 +84,7 @@ PRIVATE ui/platform/win/ui_window_win.h ui/platform/win/ui_utility_win.cpp ui/platform/win/ui_utility_win.h + ui/platform/ui_platform_window.cpp ui/platform/ui_platform_window.h ui/platform/ui_platform_utility.h ui/style/style_core.cpp diff --git a/ui/platform/linux/ui_window_linux.cpp b/ui/platform/linux/ui_window_linux.cpp index 8e2a83a..c98869a 100644 --- a/ui/platform/linux/ui_window_linux.cpp +++ b/ui/platform/linux/ui_window_linux.cpp @@ -9,7 +9,7 @@ namespace Ui { namespace Platform { -std::unique_ptr CreateWindowHelper( +std::unique_ptr CreateSpecialWindowHelper( not_null window) { return nullptr; } diff --git a/ui/platform/mac/ui_window_mac.h b/ui/platform/mac/ui_window_mac.h index ab43e42..85af0e9 100644 --- a/ui/platform/mac/ui_window_mac.h +++ b/ui/platform/mac/ui_window_mac.h @@ -32,7 +32,6 @@ private: void init(); void toggleCustomTitle(bool visible); - const not_null _window; const std::unique_ptr _private; const not_null _title; const not_null _body; diff --git a/ui/platform/mac/ui_window_mac.mm b/ui/platform/mac/ui_window_mac.mm index df9039f..f2a1837 100644 --- a/ui/platform/mac/ui_window_mac.mm +++ b/ui/platform/mac/ui_window_mac.mm @@ -132,13 +132,13 @@ Fn WindowHelper::Private::enforceStyleCallback() { } void WindowHelper::Private::initOpenGL() { - auto forceOpenGL = std::make_unique(_owner->_window); + auto forceOpenGL = std::make_unique(_owner->window()); } void WindowHelper::Private::resolveWeakPointers() { - _owner->_window->createWinId(); + _owner->window()->createWinId(); - _nativeView = reinterpret_cast(_owner->_window->winId()); + _nativeView = reinterpret_cast(_owner->window()->winId()); _nativeWindow = _nativeView ? [_nativeView window] : nullptr; Ensures(_nativeWindow != nullptr); @@ -189,14 +189,14 @@ void WindowHelper::Private::init() { } WindowHelper::WindowHelper(not_null window) -: _window(window) +: BasicWindowHelper(window) , _private(std::make_unique(this)) , _title(_private->customTitleHeight() ? Ui::CreateChild( - _window.get(), + window.get(), _private->customTitleHeight()) : nullptr) -, _body(Ui::CreateChild(_window.get())) { +, _body(Ui::CreateChild(window.get())) { init(); } @@ -211,7 +211,7 @@ void WindowHelper::setTitle(const QString &title) { if (_title) { _title->setText(title); } - _window->setWindowTitle( + window()->setWindowTitle( (!_title || _title->isHidden()) ? title : QString()); } @@ -226,29 +226,29 @@ void WindowHelper::toggleCustomTitle(bool visible) { return; } _title->setVisible(visible); - _window->setWindowTitle(visible ? QString() : _title->text()); + window()->setWindowTitle(visible ? QString() : _title->text()); } void WindowHelper::setMinimumSize(QSize size) { - _window->setMinimumSize( + window()->setMinimumSize( size.width(), (_title ? _title->height() : 0) + size.height()); } void WindowHelper::setFixedSize(QSize size) { - _window->setFixedSize( + window()->setFixedSize( size.width(), (_title ? _title->height() : 0) + size.height()); } void WindowHelper::setGeometry(QRect rect) { - _window->setGeometry( + window()->setGeometry( rect.marginsAdded({ 0, (_title ? _title->height() : 0), 0, 0 })); } void WindowHelper::init() { rpl::combine( - _window->sizeValue(), + window()->sizeValue(), _title->heightValue(), _title->shownValue() ) | rpl::start_with_next([=](QSize size, int titleHeight, bool shown) { @@ -263,7 +263,7 @@ void WindowHelper::init() { }, _body->lifetime()); } -std::unique_ptr CreateWindowHelper( +std::unique_ptr CreateSpecialWindowHelper( not_null window) { return std::make_unique(window); } diff --git a/ui/platform/ui_platform_window.cpp b/ui/platform/ui_platform_window.cpp new file mode 100644 index 0000000..9e38667 --- /dev/null +++ b/ui/platform/ui_platform_window.cpp @@ -0,0 +1,77 @@ +// 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/ui_platform_window.h" + +#include "ui/rp_widget.h" + +#include +#include + +namespace Ui { +namespace Platform { + +BasicWindowHelper::BasicWindowHelper(not_null window) +: _window(window) { +} + +not_null BasicWindowHelper::body() { + return _window; +} + +void BasicWindowHelper::setTitle(const QString &title) { + _window->setWindowTitle(title); +} + +void BasicWindowHelper::setTitleStyle(const style::WindowTitle &st) { +} + +void BasicWindowHelper::setMinimumSize(QSize size) { + _window->setMinimumSize(size); +} + +void BasicWindowHelper::setFixedSize(QSize size) { + _window->setFixedSize(size); +} + +void BasicWindowHelper::setGeometry(QRect rect) { + _window->setGeometry(rect); +} + +void BasicWindowHelper::setBodyTitleArea(Fn testMethod) { + Expects(!_bodyTitleAreaTestMethod); + + if (!testMethod) { + return; + } + _bodyTitleAreaTestMethod = std::move(testMethod); + if (customBodyTitleAreaHandling()) { + return; + } + body()->events() | rpl::start_with_next([=](not_null e) { + if (e->type() == QEvent::MouseButtonDblClick) { + if (bodyTitleAreaHit(static_cast(e.get())->pos())) { + const auto state = _window->windowState(); + if (state & Qt::WindowMaximized) { + _window->setWindowState(state & ~Qt::WindowMaximized); + } else { + _window->setWindowState(state | Qt::WindowMaximized); + } + } + } else if (e->type() == QEvent::MouseMove) { + const auto mouseEvent = static_cast(e.get()); + if (bodyTitleAreaHit(mouseEvent->pos()) + && (mouseEvent->buttons() & Qt::LeftButton)) { +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) || defined DESKTOP_APP_QT_PATCHED + _window->windowHandle()->startSystemMove(); +#endif // Qt >= 5.15 || DESKTOP_APP_QT_PATCHED + } + } + }, body()->lifetime()); +} + +} // namespace Platform +} // namespace Ui diff --git a/ui/platform/ui_platform_window.h b/ui/platform/ui_platform_window.h index 4fff950..533875f 100644 --- a/ui/platform/ui_platform_window.h +++ b/ui/platform/ui_platform_window.h @@ -18,18 +18,45 @@ namespace Platform { class BasicWindowHelper { public: - [[nodiscard]] virtual not_null body() = 0; - virtual void setTitle(const QString &title) = 0; - virtual void setTitleStyle(const style::WindowTitle &st) = 0; - virtual void setMinimumSize(QSize size) = 0; - virtual void setFixedSize(QSize size) = 0; - virtual void setGeometry(QRect rect) = 0; + explicit BasicWindowHelper(not_null window); + + [[nodiscard]] virtual not_null body(); + virtual void setTitle(const QString &title); + virtual void setTitleStyle(const style::WindowTitle &st); + virtual void setMinimumSize(QSize size); + virtual void setFixedSize(QSize size); + virtual void setGeometry(QRect rect); virtual ~BasicWindowHelper() = default; + void setBodyTitleArea(Fn testMethod); + +protected: + [[nodiscard]] not_null window() const { + return _window; + } + [[nodiscard]] bool bodyTitleAreaHit(QPoint point) const { + return _bodyTitleAreaTestMethod && _bodyTitleAreaTestMethod(point); + } + [[nodiscard]] virtual bool customBodyTitleAreaHandling() { + return false; + } + +private: + const not_null _window; + Fn _bodyTitleAreaTestMethod; + }; -[[nodiscard]] std::unique_ptr CreateWindowHelper( +[[nodiscard]] std::unique_ptr CreateSpecialWindowHelper( not_null window); +[[nodiscard]] inline std::unique_ptr CreateWindowHelper( + not_null window) { + if (auto special = CreateSpecialWindowHelper(window)) { + return special; + } + return std::make_unique(window); +} + } // namespace Platform } // namespace Ui diff --git a/ui/platform/win/ui_window_win.cpp b/ui/platform/win/ui_window_win.cpp index 934edd6..3428e1e 100644 --- a/ui/platform/win/ui_window_win.cpp +++ b/ui/platform/win/ui_window_win.cpp @@ -93,11 +93,11 @@ bool WindowHelper::NativeFilter::nativeEventFilter( } WindowHelper::WindowHelper(not_null window) -: _window(window) -, _handle(GetWindowHandle(_window)) -, _title(Ui::CreateChild(_window.get())) -, _body(Ui::CreateChild(_window.get())) -, _shadow(_window, st::windowShadowFg->c) { +: BasicWindowHelper(window) +, _handle(GetWindowHandle(window)) +, _title(Ui::CreateChild(window.get())) +, _body(Ui::CreateChild(window.get())) +, _shadow(window, st::windowShadowFg->c) { Expects(_handle != nullptr); GetNativeFilter()->registerWindow(_handle, this); @@ -114,7 +114,7 @@ not_null WindowHelper::body() { void WindowHelper::setTitle(const QString &title) { _title->setText(title); - _window->setWindowTitle(title); + window()->setWindowTitle(title); } void WindowHelper::setTitleStyle(const style::WindowTitle &st) { @@ -122,27 +122,27 @@ void WindowHelper::setTitleStyle(const style::WindowTitle &st) { } void WindowHelper::setMinimumSize(QSize size) { - _window->setMinimumSize(size.width(), _title->height() + size.height()); + window()->setMinimumSize(size.width(), _title->height() + size.height()); } void WindowHelper::setFixedSize(QSize size) { - _window->setFixedSize(size.width(), _title->height() + size.height()); + window()->setFixedSize(size.width(), _title->height() + size.height()); _title->setResizeEnabled(false); _shadow.setResizeEnabled(false); } void WindowHelper::setGeometry(QRect rect) { - _window->setGeometry(rect.marginsAdded({ 0, _title->height(), 0, 0 })); + window()->setGeometry(rect.marginsAdded({ 0, _title->height(), 0, 0 })); } void WindowHelper::init() { style::PaletteChanged( ) | rpl::start_with_next([=] { _shadow.setColor(st::windowShadowFg->c); - }, _window->lifetime()); + }, window()->lifetime()); rpl::combine( - _window->sizeValue(), + window()->sizeValue(), _title->heightValue() ) | rpl::start_with_next([=](QSize size, int titleHeight) { _body->setGeometry( @@ -165,14 +165,14 @@ void WindowHelper::init() { const auto handleStateChanged = [=](Qt::WindowState state) { updateSystemMenu(state); if (fixedSize() && (state & Qt::WindowMaximized)) { - crl::on_main(_window.get(), [=] { - _window->setWindowState( - _window->windowState() & ~Qt::WindowMaximized); + crl::on_main(window().get(), [=] { + window()->setWindowState( + window()->windowState() & ~Qt::WindowMaximized); }); } }; Ui::Connect( - _window->windowHandle(), + window()->windowHandle(), &QWindow::windowStateChanged, handleStateChanged); } @@ -186,14 +186,14 @@ bool WindowHelper::handleNativeEvent( case WM_ACTIVATE: { if (LOWORD(wParam) == WA_CLICKACTIVE) { - Ui::MarkInactivePress(_window, true); + Ui::MarkInactivePress(window(), true); } if (LOWORD(wParam) != WA_INACTIVE) { _shadow.update(WindowShadow::Change::Activate); } else { _shadow.update(WindowShadow::Change::Deactivate); } - _window->update(); + window()->update(); } return false; case WM_NCPAINT: { @@ -267,14 +267,14 @@ bool WindowHelper::handleNativeEvent( || wParam == SIZE_RESTORED || wParam == SIZE_MINIMIZED) { if (wParam != SIZE_RESTORED - || _window->windowState() != Qt::WindowNoState) { + || window()->windowState() != Qt::WindowNoState) { Qt::WindowState state = Qt::WindowNoState; if (wParam == SIZE_MAXIMIZED) { state = Qt::WindowMaximized; } else if (wParam == SIZE_MINIMIZED) { state = Qt::WindowMinimized; } - emit _window->windowHandle()->windowStateChanged(state); + emit window()->windowHandle()->windowStateChanged(state); } updateMargins(); const auto changes = (wParam == SIZE_MINIMIZED @@ -310,10 +310,12 @@ bool WindowHelper::handleNativeEvent( const auto mapped = QPoint( p.x - r.left + _marginsDelta.left(), p.y - r.top + _marginsDelta.top()); - if (!_window->rect().contains(mapped)) { + if (!window()->rect().contains(mapped)) { *result = HTTRANSPARENT; } else if (!_title->geometry().contains(mapped)) { - *result = HTCLIENT; + *result = bodyTitleAreaHit(mapped - QPoint(0, _title->height())) + ? HTCAPTION + : HTCLIENT; } else switch (_title->hitTest(_title->pos() + mapped)) { case HitTestResult::Client: case HitTestResult::SysButton: *result = HTCLIENT; break; @@ -338,7 +340,7 @@ bool WindowHelper::handleNativeEvent( case WM_SYSCOMMAND: { if (wParam == SC_MOUSEMENU && !fixedSize()) { POINTS p = MAKEPOINTS(lParam); - updateSystemMenu(_window->windowHandle()->windowState()); + updateSystemMenu(window()->windowHandle()->windowState()); TrackPopupMenu( _menu, TPM_LEFTALIGN | TPM_TOPALIGN | TPM_LEFTBUTTON, @@ -357,19 +359,19 @@ bool WindowHelper::handleNativeEvent( const auto command = LOWORD(wParam); switch (command) { case SC_CLOSE: - _window->close(); + window()->close(); return true; case SC_MINIMIZE: - _window->setWindowState( - _window->windowState() | Qt::WindowMinimized); + window()->setWindowState( + window()->windowState() | Qt::WindowMinimized); return true; case SC_MAXIMIZE: if (!fixedSize()) { - _window->setWindowState(Qt::WindowMaximized); + window()->setWindowState(Qt::WindowMaximized); } return true; case SC_RESTORE: - _window->setWindowState(Qt::WindowNoState); + window()->setWindowState(Qt::WindowNoState); return true; } } return true; @@ -379,7 +381,7 @@ bool WindowHelper::handleNativeEvent( } bool WindowHelper::fixedSize() const { - return _window->minimumSize() == _window->maximumSize(); + return window()->minimumSize() == window()->maximumSize(); } void WindowHelper::updateMargins() { @@ -444,14 +446,14 @@ void WindowHelper::updateMargins() { if (const auto native = QGuiApplication::platformNativeInterface()) { native->setWindowProperty( - _window->windowHandle()->handle(), + window()->windowHandle()->handle(), "WindowsCustomMargins", QVariant::fromValue(margins)); } } void WindowHelper::updateSystemMenu() { - updateSystemMenu(_window->windowHandle()->windowState()); + updateSystemMenu(window()->windowHandle()->windowState()); } void WindowHelper::updateSystemMenu(Qt::WindowState state) { @@ -518,7 +520,7 @@ HWND GetWindowHandle(not_null widget) { window)); } -std::unique_ptr CreateWindowHelper( +std::unique_ptr CreateSpecialWindowHelper( not_null window) { return std::make_unique(window); } diff --git a/ui/platform/win/ui_window_win.h b/ui/platform/win/ui_window_win.h index ff88284..d8003bc 100644 --- a/ui/platform/win/ui_window_win.h +++ b/ui/platform/win/ui_window_win.h @@ -32,6 +32,10 @@ private: class NativeFilter; friend class NativeFilter; + bool customBodyTitleAreaHandling() override { + return true; + } + void init(); void updateMargins(); void updateSystemMenu(); @@ -45,7 +49,6 @@ private: static not_null GetNativeFilter(); - const not_null _window; const HWND _handle = nullptr; const not_null _title; const not_null _body; diff --git a/ui/widgets/window.cpp b/ui/widgets/window.cpp index 113fb7f..2b8538e 100644 --- a/ui/widgets/window.cpp +++ b/ui/widgets/window.cpp @@ -13,55 +13,43 @@ namespace Ui { Window::Window(QWidget *parent) : RpWidget(parent) , _helper(Platform::CreateWindowHelper(this)) { + Expects(_helper != nullptr); + hide(); } Window::~Window() = default; not_null Window::body() { - return _helper ? _helper->body() : this; + return _helper->body(); } not_null Window::body() const { - return _helper ? _helper->body().get() : this; + return _helper->body().get(); } void Window::setTitle(const QString &title) { - if (_helper) { - _helper->setTitle(title); - } else { - setWindowTitle(title); - } + _helper->setTitle(title); } void Window::setTitleStyle(const style::WindowTitle &st) { - if (_helper) { - _helper->setTitleStyle(st); - } + _helper->setTitleStyle(st); } void Window::setMinimumSize(QSize size) { - if (_helper) { - _helper->setMinimumSize(size); - } else { - RpWidget::setMinimumSize(size); - } + _helper->setMinimumSize(size); } void Window::setFixedSize(QSize size) { - if (_helper) { - _helper->setFixedSize(size); - } else { - RpWidget::setFixedSize(size); - } + _helper->setFixedSize(size); } void Window::setGeometry(QRect rect) { - if (_helper) { - _helper->setGeometry(rect); - } else { - RpWidget::setGeometry(rect); - } + _helper->setGeometry(rect); +} + +void Window::setBodyTitleArea(Fn testMethod) { + _helper->setBodyTitleArea(std::move(testMethod)); } } // namespace Ui diff --git a/ui/widgets/window.h b/ui/widgets/window.h index 7a96a6a..3d2c6a8 100644 --- a/ui/widgets/window.h +++ b/ui/widgets/window.h @@ -30,6 +30,7 @@ public: void setMinimumSize(QSize size); void setFixedSize(QSize size); void setGeometry(QRect rect); + void setBodyTitleArea(Fn testMethod); private: const std::unique_ptr _helper; From 3d5108a8787305ba7949659105b7f7490363488e Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 12 Aug 2020 20:45:57 +0400 Subject: [PATCH 05/14] Allow Ui::Window to become fullscreen. --- ui/colors.palette | 1 + ui/platform/ui_platform_window.cpp | 60 +++++++++++++++++++++++++----- ui/platform/ui_platform_window.h | 23 ++++++++---- ui/platform/win/ui_window_win.cpp | 23 ++++++++++-- ui/platform/win/ui_window_win.h | 7 ++-- ui/widgets/window.cpp | 11 +++++- ui/widgets/window.h | 16 +++++++- 7 files changed, 114 insertions(+), 27 deletions(-) diff --git a/ui/colors.palette b/ui/colors.palette index 6231b0f..f13b568 100644 --- a/ui/colors.palette +++ b/ui/colors.palette @@ -538,6 +538,7 @@ notificationBg: windowBg; // custom notification window background // calls callBg: #26282cf2; // old phone call popup background callBgOpaque: #1b1f23 | callBg; // phone call popup background +callBgButton: #1b1f2356 | callBg; // phone call window control buttons bg callNameFg: #ffffff; // phone call popup name text callFingerprintBg: #00000066; // phone call popup emoji fingerprint background callStatusFg: #aaabac; // phone call popup status text diff --git a/ui/platform/ui_platform_window.cpp b/ui/platform/ui_platform_window.cpp index 9e38667..a7ebaa5 100644 --- a/ui/platform/ui_platform_window.cpp +++ b/ui/platform/ui_platform_window.cpp @@ -6,7 +6,7 @@ // #include "ui/platform/ui_platform_window.h" -#include "ui/rp_widget.h" +#include "ui/widgets/window.h" #include #include @@ -41,34 +41,74 @@ void BasicWindowHelper::setGeometry(QRect rect) { _window->setGeometry(rect); } -void BasicWindowHelper::setBodyTitleArea(Fn testMethod) { +void BasicWindowHelper::showFullScreen() { + _window->showFullScreen(); +} + +void BasicWindowHelper::showNormal() { + _window->showNormal(); +} + +void BasicWindowHelper::setBodyTitleArea( + Fn testMethod) { Expects(!_bodyTitleAreaTestMethod); if (!testMethod) { return; } _bodyTitleAreaTestMethod = std::move(testMethod); - if (customBodyTitleAreaHandling()) { - return; - } body()->events() | rpl::start_with_next([=](not_null e) { + const auto hitTest = [&] { + return bodyTitleAreaHit( + static_cast(e.get())->pos()); + }; if (e->type() == QEvent::MouseButtonDblClick) { - if (bodyTitleAreaHit(static_cast(e.get())->pos())) { + _mousePressed = false; + const auto hit = hitTest(); + if (hit & WindowTitleHitTestFlag::Maximize) { const auto state = _window->windowState(); if (state & Qt::WindowMaximized) { _window->setWindowState(state & ~Qt::WindowMaximized); } else { _window->setWindowState(state | Qt::WindowMaximized); } + } else if (hit & WindowTitleHitTestFlag::FullScreen) { + if (_window->isFullScreen()) { + showNormal(); + } else { + showFullScreen(); + } } + } else if (e->type() == QEvent::MouseButtonRelease) { + _mousePressed = false; + } else if (e->type() == QEvent::MouseButtonPress + && (static_cast(e.get())->button() + == Qt::LeftButton)) { + _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 (bodyTitleAreaHit(mouseEvent->pos()) - && (mouseEvent->buttons() & Qt::LeftButton)) { -#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) || defined DESKTOP_APP_QT_PATCHED + if (_mousePressed +#ifndef Q_OS_WIN // We handle fullscreen startSystemMove() only on Windows. + && !_window->isFullScreen() +#endif // !Q_OS_WIN + && (hitTest() & WindowTitleHitTestFlag::Move)) { + +#ifdef Q_OS_WIN + if (_window->isFullScreen()) { + // On Windows we just jump out of fullscreen + // like we do automatically for dragging a window + // by title bar in a maximized state. + showNormal(); + } +#endif // Q_OS_WIN + _mousePressed = false; _window->windowHandle()->startSystemMove(); -#endif // Qt >= 5.15 || DESKTOP_APP_QT_PATCHED } +#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 533875f..8932b05 100644 --- a/ui/platform/ui_platform_window.h +++ b/ui/platform/ui_platform_window.h @@ -6,6 +6,8 @@ // #pragma once +#include "base/flags.h" + namespace style { struct WindowTitle; } // namespace style @@ -13,12 +15,15 @@ struct WindowTitle; namespace Ui { class RpWidget; +enum class WindowTitleHitTestFlag; +using WindowTitleHitTestFlags = base::flags; namespace Platform { class BasicWindowHelper { public: explicit BasicWindowHelper(not_null window); + virtual ~BasicWindowHelper() = default; [[nodiscard]] virtual not_null body(); virtual void setTitle(const QString &title); @@ -26,24 +31,26 @@ public: virtual void setMinimumSize(QSize size); virtual void setFixedSize(QSize size); virtual void setGeometry(QRect rect); - virtual ~BasicWindowHelper() = default; + virtual void showFullScreen(); + virtual void showNormal(); - void setBodyTitleArea(Fn testMethod); + void setBodyTitleArea(Fn testMethod); protected: [[nodiscard]] not_null window() const { return _window; } - [[nodiscard]] bool bodyTitleAreaHit(QPoint point) const { - return _bodyTitleAreaTestMethod && _bodyTitleAreaTestMethod(point); - } - [[nodiscard]] virtual bool customBodyTitleAreaHandling() { - return false; + [[nodiscard]] WindowTitleHitTestFlags bodyTitleAreaHit( + QPoint point) const { + return _bodyTitleAreaTestMethod + ? _bodyTitleAreaTestMethod(point) + : WindowTitleHitTestFlag(); } private: const not_null _window; - Fn _bodyTitleAreaTestMethod; + Fn _bodyTitleAreaTestMethod; + bool _mousePressed = false; }; diff --git a/ui/platform/win/ui_window_win.cpp b/ui/platform/win/ui_window_win.cpp index 3428e1e..5e2652b 100644 --- a/ui/platform/win/ui_window_win.cpp +++ b/ui/platform/win/ui_window_win.cpp @@ -135,6 +135,22 @@ void WindowHelper::setGeometry(QRect rect) { window()->setGeometry(rect.marginsAdded({ 0, _title->height(), 0, 0 })); } +void WindowHelper::showFullScreen() { + if (!_isFullScreen) { + _isFullScreen = true; + updateMargins(); + } + window()->showFullScreen(); +} + +void WindowHelper::showNormal() { + window()->showNormal(); + if (_isFullScreen) { + _isFullScreen = false; + updateMargins(); + } +} + void WindowHelper::init() { style::PaletteChanged( ) | rpl::start_with_next([=] { @@ -313,9 +329,7 @@ bool WindowHelper::handleNativeEvent( if (!window()->rect().contains(mapped)) { *result = HTTRANSPARENT; } else if (!_title->geometry().contains(mapped)) { - *result = bodyTitleAreaHit(mapped - QPoint(0, _title->height())) - ? HTCAPTION - : HTCLIENT; + *result = HTCLIENT; } else switch (_title->hitTest(_title->pos() + mapped)) { case HitTestResult::Client: case HitTestResult::SysButton: *result = HTCLIENT; break; @@ -444,6 +458,9 @@ void WindowHelper::updateMargins() { _marginsDelta = QMargins(); } + if (_isFullScreen) { + margins = QMargins(); + } if (const auto native = QGuiApplication::platformNativeInterface()) { native->setWindowProperty( window()->windowHandle()->handle(), diff --git a/ui/platform/win/ui_window_win.h b/ui/platform/win/ui_window_win.h index d8003bc..c74eb0b 100644 --- a/ui/platform/win/ui_window_win.h +++ b/ui/platform/win/ui_window_win.h @@ -27,15 +27,13 @@ public: void setMinimumSize(QSize size) override; void setFixedSize(QSize size) override; void setGeometry(QRect rect) override; + void showFullScreen() override; + void showNormal() override; private: class NativeFilter; friend class NativeFilter; - bool customBodyTitleAreaHandling() override { - return true; - } - void init(); void updateMargins(); void updateSystemMenu(); @@ -56,6 +54,7 @@ private: bool _updatingMargins = false; QMargins _marginsDelta; HMENU _menu = nullptr; + bool _isFullScreen = false; }; diff --git a/ui/widgets/window.cpp b/ui/widgets/window.cpp index 2b8538e..382d4fb 100644 --- a/ui/widgets/window.cpp +++ b/ui/widgets/window.cpp @@ -48,7 +48,16 @@ void Window::setGeometry(QRect rect) { _helper->setGeometry(rect); } -void Window::setBodyTitleArea(Fn testMethod) { +void Window::showFullScreen() { + _helper->showFullScreen(); +} + +void Window::showNormal() { + _helper->showNormal(); +} + +void Window::setBodyTitleArea( + Fn testMethod) { _helper->setBodyTitleArea(std::move(testMethod)); } diff --git a/ui/widgets/window.h b/ui/widgets/window.h index 3d2c6a8..73dcd17 100644 --- a/ui/widgets/window.h +++ b/ui/widgets/window.h @@ -7,6 +7,7 @@ #pragma once #include "ui/rp_widget.h" +#include "base/flags.h" namespace style { struct WindowTitle; @@ -17,6 +18,17 @@ namespace Platform { class BasicWindowHelper; } // namespace Platform +enum class WindowTitleHitTestFlag { + None = 0x00, + Move = 0x01, + Maximize = 0x02, + FullScreen = 0x04, +}; +inline constexpr bool is_flag_type(WindowTitleHitTestFlag) { + return true; +} +using WindowTitleHitTestFlags = base::flags; + class Window : public RpWidget { public: explicit Window(QWidget *parent = nullptr); @@ -30,7 +42,9 @@ public: void setMinimumSize(QSize size); void setFixedSize(QSize size); void setGeometry(QRect rect); - void setBodyTitleArea(Fn testMethod); + void showFullScreen(); + void showNormal(); + void setBodyTitleArea(Fn testMethod); private: const std::unique_ptr _helper; From c45b62084ce9e54432f8d52c082856e07b5ad28c Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 12 Aug 2020 21:00:03 +0400 Subject: [PATCH 06/14] Support hidden custom title on macOS. --- ui/platform/mac/ui_window_mac.mm | 9 +++++++++ ui/platform/mac/ui_window_title_mac.h | 1 + ui/platform/mac/ui_window_title_mac.mm | 4 ++++ 3 files changed, 14 insertions(+) diff --git a/ui/platform/mac/ui_window_mac.mm b/ui/platform/mac/ui_window_mac.mm index f2a1837..8af0e0b 100644 --- a/ui/platform/mac/ui_window_mac.mm +++ b/ui/platform/mac/ui_window_mac.mm @@ -197,6 +197,9 @@ WindowHelper::WindowHelper(not_null window) _private->customTitleHeight()) : nullptr) , _body(Ui::CreateChild(window.get())) { + if (_title->shouldBeHidden()) { + toggleCustomTitle(false); + } init(); } @@ -218,10 +221,16 @@ void WindowHelper::setTitle(const QString &title) { void WindowHelper::setTitleStyle(const style::WindowTitle &st) { if (_title) { _title->setStyle(st); + if (_title->shouldBeHidden()) { + toggleCustomTitle(false); + } } } void WindowHelper::toggleCustomTitle(bool visible) { + if (_title->shouldBeHidden()) { + visible = false; + } if (!_title || _title->isHidden() != visible) { return; } diff --git a/ui/platform/mac/ui_window_title_mac.h b/ui/platform/mac/ui_window_title_mac.h index af31a29..3aac917 100644 --- a/ui/platform/mac/ui_window_title_mac.h +++ b/ui/platform/mac/ui_window_title_mac.h @@ -29,6 +29,7 @@ public: void setText(const QString &text); void setStyle(const style::WindowTitle &st); [[nodiscard]] QString text() const; + [[nodiscard]] bool shouldBeHidden() const; protected: void paintEvent(QPaintEvent *e) override; diff --git a/ui/platform/mac/ui_window_title_mac.mm b/ui/platform/mac/ui_window_title_mac.mm index abd431b..d768122 100644 --- a/ui/platform/mac/ui_window_title_mac.mm +++ b/ui/platform/mac/ui_window_title_mac.mm @@ -38,6 +38,10 @@ void TitleWidget::setStyle(const style::WindowTitle &st) { update(); } +bool TitleWidget::shouldBeHidden() const { + return !_st->height; +} + QString TitleWidget::text() const { return _text; } From 6735ee93dc6202d816fe1e3c06187007e2633565 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 13 Aug 2020 13:00:42 +0400 Subject: [PATCH 07/14] 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; From b534567e11cd9c39035374364448d2aa76bd0aab Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 13 Aug 2020 14:31:41 +0400 Subject: [PATCH 08/14] Extract TitleControls from TitleWidget. --- ui/colors.palette | 2 +- ui/platform/win/ui_window_title_win.cpp | 166 ++++++++++++++++-------- ui/platform/win/ui_window_title_win.h | 52 +++++--- 3 files changed, 146 insertions(+), 74 deletions(-) diff --git a/ui/colors.palette b/ui/colors.palette index f13b568..646a4fa 100644 --- a/ui/colors.palette +++ b/ui/colors.palette @@ -538,7 +538,7 @@ notificationBg: windowBg; // custom notification window background // calls callBg: #26282cf2; // old phone call popup background callBgOpaque: #1b1f23 | callBg; // phone call popup background -callBgButton: #1b1f2356 | callBg; // phone call window control buttons bg +callBgButton: #1b1f237f | callBg; // phone call window control buttons bg callNameFg: #ffffff; // phone call popup name text callFingerprintBg: #00000066; // phone call popup emoji fingerprint background callStatusFg: #aaabac; // phone call popup status text diff --git a/ui/platform/win/ui_window_title_win.cpp b/ui/platform/win/ui_window_title_win.cpp index d08e332..0def675 100644 --- a/ui/platform/win/ui_window_title_win.cpp +++ b/ui/platform/win/ui_window_title_win.cpp @@ -19,39 +19,59 @@ namespace Ui { namespace Platform { -TitleWidget::TitleWidget(not_null parent) -: RpWidget(parent) -, _st(&st::defaultWindowTitle) -, _minimize(this, _st->minimize) -, _maximizeRestore(this, _st->maximize) -, _close(this, _st->close) -, _shadow(this, st::titleShadow) -, _maximizedState(parent->windowState() & Qt::WindowMaximized) +TitleControls::TitleControls( + not_null parent, + Fn maximize) +: _st(&st::defaultWindowTitle) +, _minimize(parent, _st->minimize) +, _maximizeRestore(parent, _st->maximize) +, _close(parent, _st->close) +, _maximizedState(parent->windowState() + & (Qt::WindowMaximized | Qt::WindowFullScreen)) , _activeState(parent->isActiveWindow()) { - init(); + init(std::move(maximize)); + + _close->paintRequest( + ) | rpl::start_with_next([=] { + const auto active = window()->isActiveWindow(); + if (_activeState != active) { + _activeState = active; + updateButtonsState(); + } + }, _close->lifetime()); } -void TitleWidget::setText(const QString &text) { - window()->setWindowTitle(text); -} - -void TitleWidget::setStyle(const style::WindowTitle &st) { +void TitleControls::setStyle(const style::WindowTitle &st) { _st = &st; - setGeometry(0, 0, window()->width(), _st->height); updateButtonsState(); - update(); } -not_null TitleWidget::window() const { - return static_cast(parentWidget()); +not_null TitleControls::st() const { + return _st; } -void TitleWidget::setResizeEnabled(bool enabled) { - _resizeEnabled = enabled; - updateControlsVisibility(); +QRect TitleControls::geometry() const { + auto result = QRect(); + const auto add = [&](auto &&control) { + if (!control->isHidden()) { + result = result.united(control->geometry()); + } + }; + add(_minimize); + add(_maximizeRestore); + add(_close); + return result; } -void TitleWidget::init() { +not_null TitleControls::parent() const { + return static_cast(_close->parentWidget()); +} + +not_null TitleControls::window() const { + return _close->window(); +} + +void TitleControls::init(Fn maximize) { _minimize->setClickedCallback([=] { window()->setWindowState( window()->windowState() | Qt::WindowMinimized); @@ -59,9 +79,13 @@ void TitleWidget::init() { }); _minimize->setPointerCursor(false); _maximizeRestore->setClickedCallback([=] { - window()->setWindowState(_maximizedState - ? Qt::WindowNoState - : Qt::WindowMaximized); + if (maximize) { + maximize(!_maximizedState); + } else { + window()->setWindowState(_maximizedState + ? Qt::WindowNoState + : Qt::WindowMaximized); + } _maximizeRestore->clearState(); }); _maximizeRestore->setPointerCursor(false); @@ -71,32 +95,32 @@ void TitleWidget::init() { }); _close->setPointerCursor(false); - setAttribute(Qt::WA_OpaquePaintEvent); - - window()->widthValue( + parent()->widthValue( ) | rpl::start_with_next([=](int width) { - setGeometry(0, 0, width, _st->height); - }, lifetime()); + updateControlsPosition(); + }, _close->lifetime()); window()->createWinId(); - connect( + QObject::connect( window()->windowHandle(), &QWindow::windowStateChanged, [=](Qt::WindowState state) { handleWindowStateChanged(state); }); - _activeState = isActiveWindow(); + _activeState = parent()->isActiveWindow(); updateButtonsState(); } -void TitleWidget::paintEvent(QPaintEvent *e) { - const auto active = isActiveWindow(); - if (_activeState != active) { - _activeState = active; - updateButtonsState(); - } - QPainter(this).fillRect(e->rect(), active ? _st->bgActive : _st->bg); +void TitleControls::setResizeEnabled(bool enabled) { + _resizeEnabled = enabled; + updateControlsVisibility(); } -void TitleWidget::updateControlsPosition() { +void TitleControls::raise() { + _minimize->raise(); + _maximizeRestore->raise(); + _close->raise(); +} + +void TitleControls::updateControlsPosition() { auto right = 0; _close->moveToRight(right, 0); right += _close->width(); _maximizeRestore->moveToRight(right, 0); @@ -106,28 +130,25 @@ void TitleWidget::updateControlsPosition() { _minimize->moveToRight(right, 0); } -void TitleWidget::resizeEvent(QResizeEvent *e) { - updateControlsPosition(); - _shadow->setGeometry(0, height() - st::lineWidth, width(), st::lineWidth); -} - -void TitleWidget::updateControlsVisibility() { +void TitleControls::updateControlsVisibility() { _maximizeRestore->setVisible(_resizeEnabled); updateControlsPosition(); - update(); } -void TitleWidget::handleWindowStateChanged(Qt::WindowState state) { - if (state == Qt::WindowMinimized) return; +void TitleControls::handleWindowStateChanged(Qt::WindowState state) { + if (state == Qt::WindowMinimized) { + return; + } - auto maximized = (state == Qt::WindowMaximized); + auto maximized = (state == Qt::WindowMaximized) + || (state == Qt::WindowFullScreen); if (_maximizedState != maximized) { _maximizedState = maximized; updateButtonsState(); } } -void TitleWidget::updateButtonsState() { +void TitleControls::updateButtonsState() { const auto minimize = _activeState ? &_st->minimizeIconActive : &_st->minimize.icon; @@ -161,12 +182,45 @@ void TitleWidget::updateButtonsState() { _close->setIconOverride(close, closeOver); } +TitleWidget::TitleWidget(not_null parent) +: RpWidget(parent) +, _controls(this) +, _shadow(this, st::titleShadow) { + setAttribute(Qt::WA_OpaquePaintEvent); + + parent->widthValue( + ) | rpl::start_with_next([=](int width) { + setGeometry(0, 0, width, _controls.st()->height); + }, lifetime()); +} + +void TitleWidget::setText(const QString &text) { + window()->setWindowTitle(text); +} + +void TitleWidget::setStyle(const style::WindowTitle &st) { + _controls.setStyle(st); + setGeometry(0, 0, window()->width(), _controls.st()->height); + update(); +} + +void TitleWidget::setResizeEnabled(bool enabled) { + _controls.setResizeEnabled(enabled); +} + +void TitleWidget::paintEvent(QPaintEvent *e) { + const auto active = window()->isActiveWindow(); + QPainter(this).fillRect( + e->rect(), + active ? _controls.st()->bgActive : _controls.st()->bg); +} + +void TitleWidget::resizeEvent(QResizeEvent *e) { + _shadow->setGeometry(0, height() - st::lineWidth, width(), st::lineWidth); +} + HitTestResult TitleWidget::hitTest(QPoint point) const { - if (false - || (_minimize->geometry().contains(point)) - || (_maximizeRestore->geometry().contains(point)) - || (_close->geometry().contains(point)) - ) { + if (_controls.geometry().contains(point)) { return HitTestResult::SysButton; } else if (rect().contains(point)) { return HitTestResult::Caption; diff --git a/ui/platform/win/ui_window_title_win.h b/ui/platform/win/ui_window_title_win.h index 25b706f..7deacb0 100644 --- a/ui/platform/win/ui_window_title_win.h +++ b/ui/platform/win/ui_window_title_win.h @@ -38,6 +38,40 @@ enum class HitTestResult { TopLeft, }; +class TitleControls final { +public: + TitleControls( + not_null parent, + Fn maximize = nullptr); + + void setStyle(const style::WindowTitle &st); + [[nodiscard]] not_null st() const; + [[nodiscard]] QRect geometry() const; + void setResizeEnabled(bool enabled); + void raise(); + +private: + [[nodiscard]] not_null parent() const; + [[nodiscard]] not_null window() const; + + void init(Fn maximize); + void updateControlsVisibility(); + void updateButtonsState(); + void updateControlsPosition(); + void handleWindowStateChanged(Qt::WindowState state = Qt::WindowNoState); + + not_null _st; + + object_ptr _minimize; + object_ptr _maximizeRestore; + object_ptr _close; + + bool _maximizedState = false; + bool _activeState = false; + bool _resizeEnabled = true; + +}; + class TitleWidget : public RpWidget { public: explicit TitleWidget(not_null parent); @@ -52,25 +86,9 @@ protected: void resizeEvent(QResizeEvent *e) override; private: - not_null window() const; - - void init(); - void handleWindowStateChanged(Qt::WindowState state = Qt::WindowNoState); - void updateControlsVisibility(); - void updateButtonsState(); - void updateControlsPosition(); - - not_null _st; - - object_ptr _minimize; - object_ptr _maximizeRestore; - object_ptr _close; + TitleControls _controls; object_ptr _shadow; - bool _maximizedState = false; - bool _activeState = false; - bool _resizeEnabled = true; - }; } // namespace Platform From 4c04017737f7b80bd0a5032e732c2c75efd03f8e Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 13 Aug 2020 15:10:42 +0400 Subject: [PATCH 09/14] Fix checks in small image round request. --- ui/image/image_prepare.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ui/image/image_prepare.cpp b/ui/image/image_prepare.cpp index c774456..d372b22 100644 --- a/ui/image/image_prepare.cpp +++ b/ui/image/image_prepare.cpp @@ -458,9 +458,9 @@ void prepareRound( } auto cornerWidth = cornerMasks[0].width(); auto cornerHeight = cornerMasks[0].height(); - auto imageWidth = image.width(); - auto imageHeight = image.height(); - if (imageWidth < 2 * cornerWidth || imageHeight < 2 * cornerHeight) { + auto targetWidth = target.width(); + auto targetHeight = target.height(); + if (targetWidth < 2 * cornerWidth || targetHeight < 2 * cornerHeight) { return; } @@ -475,9 +475,9 @@ void prepareRound( Assert(image.depth() == static_cast((imageIntsPerPixel * sizeof(uint32)) << 3)); Assert(image.bytesPerLine() == (imageIntsPerLine << 2)); auto intsTopLeft = ints + target.x() + target.y() * imageIntsPerLine; - auto intsTopRight = ints + target.x() + target.width() - cornerWidth + target.y() * imageIntsPerLine; - auto intsBottomLeft = ints + target.x() + (target.y() + target.height() - cornerHeight) * imageIntsPerLine; - auto intsBottomRight = ints + target.x() + target.width() - cornerWidth + (target.y() + target.height() - cornerHeight) * imageIntsPerLine; + auto intsTopRight = ints + target.x() + targetWidth - cornerWidth + target.y() * imageIntsPerLine; + auto intsBottomLeft = ints + target.x() + (target.y() + targetHeight - cornerHeight) * imageIntsPerLine; + auto intsBottomRight = ints + target.x() + targetWidth - cornerWidth + (target.y() + targetHeight - cornerHeight) * imageIntsPerLine; auto maskCorner = [&](uint32 *imageInts, const QImage &mask) { auto maskWidth = mask.width(); auto maskHeight = mask.height(); From 921686055c2b8b3a47a7f81840339a5a716b4edd Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 13 Aug 2020 17:58:03 +0400 Subject: [PATCH 10/14] Pass title controls style in constructor. --- ui/platform/win/ui_window_title_win.cpp | 5 +++-- ui/platform/win/ui_window_title_win.h | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/ui/platform/win/ui_window_title_win.cpp b/ui/platform/win/ui_window_title_win.cpp index 0def675..c28cf16 100644 --- a/ui/platform/win/ui_window_title_win.cpp +++ b/ui/platform/win/ui_window_title_win.cpp @@ -21,8 +21,9 @@ namespace Platform { TitleControls::TitleControls( not_null parent, + const style::WindowTitle &st, Fn maximize) -: _st(&st::defaultWindowTitle) +: _st(&st) , _minimize(parent, _st->minimize) , _maximizeRestore(parent, _st->maximize) , _close(parent, _st->close) @@ -184,7 +185,7 @@ void TitleControls::updateButtonsState() { TitleWidget::TitleWidget(not_null parent) : RpWidget(parent) -, _controls(this) +, _controls(this, st::defaultWindowTitle) , _shadow(this, st::titleShadow) { setAttribute(Qt::WA_OpaquePaintEvent); diff --git a/ui/platform/win/ui_window_title_win.h b/ui/platform/win/ui_window_title_win.h index 7deacb0..6515065 100644 --- a/ui/platform/win/ui_window_title_win.h +++ b/ui/platform/win/ui_window_title_win.h @@ -42,6 +42,7 @@ class TitleControls final { public: TitleControls( not_null parent, + const style::WindowTitle &st, Fn maximize = nullptr); void setStyle(const style::WindowTitle &st); From 50aa8497c290f23c496b62882921a6143e9d8321 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 13 Aug 2020 18:44:49 +0400 Subject: [PATCH 11/14] Add some more call panel button colors. --- ui/colors.palette | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/ui/colors.palette b/ui/colors.palette index 646a4fa..4170c81 100644 --- a/ui/colors.palette +++ b/ui/colors.palette @@ -542,16 +542,17 @@ callBgButton: #1b1f237f | callBg; // phone call window control buttons bg callNameFg: #ffffff; // phone call popup name text callFingerprintBg: #00000066; // phone call popup emoji fingerprint background callStatusFg: #aaabac; // phone call popup status text -callIconFg: #ffffff; // phone call popup answer, hangup and mute mic icon +callIconBg: #ffffff1f; // phone call mute mic and camera button background +callIconFg: #ffffff; // phone call popup answer, hangup, mute mic and camera icon +callIconBgActive: #ffffffe5; // phone call line busy cancel, muted mic and camera button background +callIconFgActive: #222222; // phone call line busy cancel, muted mic and camera icon +callIconActiveRipple: #f1f1f1; // phone call line busy cancel, muted mic and camera ripple effect callAnswerBg: #64c15b; // phone call popup answer button background callAnswerRipple: #52b149; // phone call popup answer button ripple effect callAnswerBgOuter: #50eb4126; // phone call popup answer button outer ripple effect callHangupBg: #d75a5a; // phone call popup hangup button background callHangupRipple: #c04646; // phone call popup hangup button ripple effect -callCancelBg: #ffffff; // phone call popup line busy cancel button background -callCancelFg: #777777; // phone call popup line busy cancel button icon -callCancelRipple: #f1f1f1; // phone call popup line busy cancel button ripple effect -callMuteRipple: #ffffff12; // phone call popup mute mic ripple effect +callMuteRipple: #ffffff12; // phone call popup mute mic and camera ripple effect callBarBg: dialogsBgActive; // active phone call bar background callBarMuteRipple: dialogsRippleBgActive; // active phone call bar mute and hangup button ripple effect From f2f592be817edc289eaff0248f3e41c945b736bd Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 14 Aug 2020 16:51:04 +0400 Subject: [PATCH 12/14] Remove fallback colors for new calls. --- ui/colors.palette | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/colors.palette b/ui/colors.palette index 4170c81..7ee8530 100644 --- a/ui/colors.palette +++ b/ui/colors.palette @@ -537,8 +537,8 @@ notificationBg: windowBg; // custom notification window background // calls callBg: #26282cf2; // old phone call popup background -callBgOpaque: #1b1f23 | callBg; // phone call popup background -callBgButton: #1b1f237f | callBg; // phone call window control buttons bg +callBgOpaque: #1b1f23; // phone call popup background +callBgButton: #1b1f237f; // phone call window control buttons bg callNameFg: #ffffff; // phone call popup name text callFingerprintBg: #00000066; // phone call popup emoji fingerprint background callStatusFg: #aaabac; // phone call popup status text From a1dbca85783289d737ce0368f5ec3659b5745458 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 14 Aug 2020 19:58:55 +0400 Subject: [PATCH 13/14] Fix closing Ui::Window from FullScreen on macOS. --- ui/platform/mac/ui_window_mac.h | 1 + ui/platform/mac/ui_window_mac.mm | 22 ++++++++++++++++++---- ui/platform/ui_platform_window.cpp | 4 ++++ ui/platform/ui_platform_window.h | 1 + ui/widgets/window.cpp | 4 ++++ ui/widgets/window.h | 1 + 6 files changed, 29 insertions(+), 4 deletions(-) diff --git a/ui/platform/mac/ui_window_mac.h b/ui/platform/mac/ui_window_mac.h index 3bb1970..6653079 100644 --- a/ui/platform/mac/ui_window_mac.h +++ b/ui/platform/mac/ui_window_mac.h @@ -24,6 +24,7 @@ public: void setMinimumSize(QSize size) override; void setFixedSize(QSize size) override; void setGeometry(QRect rect) override; + void close() override; private: class Private; diff --git a/ui/platform/mac/ui_window_mac.mm b/ui/platform/mac/ui_window_mac.mm index 9ecdfdf..a67a1f2 100644 --- a/ui/platform/mac/ui_window_mac.mm +++ b/ui/platform/mac/ui_window_mac.mm @@ -110,11 +110,13 @@ private: class WindowHelper::Private final { public: explicit Private(not_null owner); + ~Private(); [[nodiscard]] int customTitleHeight() const; [[nodiscard]] QRect controlsRect() const; [[nodiscard]] bool checkNativeMove(void *nswindow) const; void activateBeforeNativeMove(); + void close(); private: void init(); @@ -143,6 +145,10 @@ WindowHelper::Private::Private(not_null owner) init(); } +WindowHelper::Private::~Private() { + [_observer release]; +} + int WindowHelper::Private::customTitleHeight() const { return _customTitleHeight; } @@ -192,18 +198,22 @@ void WindowHelper::Private::activateBeforeNativeMove() { [_nativeWindow makeKeyAndOrderFront:_nativeWindow]; } +void WindowHelper::Private::close() { + [_nativeWindow close]; +} + Fn WindowHelper::Private::toggleCustomTitleCallback() { - return [=](bool visible) { + return crl::guard(_owner->window(), [=](bool visible) { _owner->toggleCustomTitle(visible); - }; + }); } Fn WindowHelper::Private::enforceStyleCallback() { - return [=] { + return crl::guard(_owner->window(), [=] { if (_nativeWindow && _customTitleHeight > 0) { [_nativeWindow setStyleMask:[_nativeWindow styleMask] | NSFullSizeContentViewWindowMask]; } - }; + }); } void WindowHelper::Private::initOpenGL() { @@ -345,6 +355,10 @@ void WindowHelper::setupBodyTitleAreaEvents() { })); } +void WindowHelper::close() { + _private->close(); +} + void WindowHelper::init() { rpl::combine( window()->sizeValue(), diff --git a/ui/platform/ui_platform_window.cpp b/ui/platform/ui_platform_window.cpp index 4ee9ea0..b45cdf4 100644 --- a/ui/platform/ui_platform_window.cpp +++ b/ui/platform/ui_platform_window.cpp @@ -49,6 +49,10 @@ void BasicWindowHelper::showNormal() { _window->showNormal(); } +void BasicWindowHelper::close() { + _window->close(); +} + void BasicWindowHelper::setBodyTitleArea( Fn testMethod) { Expects(!_bodyTitleAreaTestMethod); diff --git a/ui/platform/ui_platform_window.h b/ui/platform/ui_platform_window.h index 46de849..0844545 100644 --- a/ui/platform/ui_platform_window.h +++ b/ui/platform/ui_platform_window.h @@ -33,6 +33,7 @@ public: virtual void setGeometry(QRect rect); virtual void showFullScreen(); virtual void showNormal(); + virtual void close(); void setBodyTitleArea(Fn testMethod); diff --git a/ui/widgets/window.cpp b/ui/widgets/window.cpp index 382d4fb..a105875 100644 --- a/ui/widgets/window.cpp +++ b/ui/widgets/window.cpp @@ -56,6 +56,10 @@ void Window::showNormal() { _helper->showNormal(); } +void Window::close() { + _helper->close(); +} + void Window::setBodyTitleArea( Fn testMethod) { _helper->setBodyTitleArea(std::move(testMethod)); diff --git a/ui/widgets/window.h b/ui/widgets/window.h index 73dcd17..22e98ff 100644 --- a/ui/widgets/window.h +++ b/ui/widgets/window.h @@ -44,6 +44,7 @@ public: void setGeometry(QRect rect); void showFullScreen(); void showNormal(); + void close(); void setBodyTitleArea(Fn testMethod); private: From b5d6851c976ef87a9f662a8aaae3db45d6141cef Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 20 Aug 2020 17:57:42 +0400 Subject: [PATCH 14/14] Fix build with Qt 5.6.2. --- ui/platform/mac/ui_window_mac.h | 8 ++++++++ ui/platform/mac/ui_window_mac.mm | 30 ++++++++++++++++++++++++++++-- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/ui/platform/mac/ui_window_mac.h b/ui/platform/mac/ui_window_mac.h index 6653079..717c5e3 100644 --- a/ui/platform/mac/ui_window_mac.h +++ b/ui/platform/mac/ui_window_mac.h @@ -39,6 +39,14 @@ private: const not_null _title; const not_null _body; +#ifdef OS_OSX + struct WindowDrag { + QPoint windowStartPosition; + QPoint dragStartPosition; + }; + std::optional _drag; +#endif // OS_OSX + }; } // namespace Platform diff --git a/ui/platform/mac/ui_window_mac.mm b/ui/platform/mac/ui_window_mac.mm index a67a1f2..550584e 100644 --- a/ui/platform/mac/ui_window_mac.mm +++ b/ui/platform/mac/ui_window_mac.mm @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -188,8 +189,10 @@ bool WindowHelper::Private::checkNativeMove(void *nswindow) const { || ([_nativeWindow styleMask] & NSFullScreenWindowMask) == NSFullScreenWindowMask) { return false; } - const auto real = QPointF::fromCGPoint([NSEvent mouseLocation]); - const auto frame = QRectF::fromCGRect([_nativeWindow frame]); + 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); } @@ -341,6 +344,7 @@ void WindowHelper::setGeometry(QRect rect) { } void WindowHelper::setupBodyTitleAreaEvents() { +#ifndef OS_OSX const auto controls = _private->controlsRect(); qApp->installNativeEventFilter(new EventFilter(window(), [=](void *nswindow) { const auto point = body()->mapFromGlobal(QCursor::pos()); @@ -353,6 +357,28 @@ void WindowHelper::setupBodyTitleAreaEvents() { } return false; })); +#else // OS_OSX + // OS X 10.10 doesn't have performWindowDragWithEvent yet. + body()->events() | rpl::start_with_next([=](not_null e) { + const auto hitTest = [&] { + return bodyTitleAreaHit( + static_cast(e.get())->pos()); + }; + if (e->type() == QEvent::MouseButtonRelease + && (static_cast(e.get())->button() + == Qt::LeftButton)) { + _drag = std::nullopt; + } else if (e->type() == QEvent::MouseButtonPress + && hitTest() + && (static_cast(e.get())->button() + == Qt::LeftButton)) { + _drag = { window()->pos(), static_cast(e.get())->globalPos() }; + } else if (e->type() == QEvent::MouseMove && _drag && !window()->isFullScreen()) { + const auto delta = static_cast(e.get())->globalPos() - _drag->dragStartPosition; + window()->move(_drag->windowStartPosition + delta); + } + }, body()->lifetime()); +#endif // OS_OSX } void WindowHelper::close() {