diff --git a/CMakeLists.txt b/CMakeLists.txt index 562ba9c..ecf3f2b 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) @@ -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/colors.palette b/ui/colors.palette index 4516f3d..7ee8530 100644 --- a/ui/colors.palette +++ b/ui/colors.palette @@ -536,20 +536,23 @@ 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; // 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 -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 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(); 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..717c5e3 100644 --- a/ui/platform/mac/ui_window_mac.h +++ b/ui/platform/mac/ui_window_mac.h @@ -24,19 +24,29 @@ public: void setMinimumSize(QSize size) override; void setFixedSize(QSize size) override; void setGeometry(QRect rect) override; + void close() override; private: class Private; friend class Private; + void setupBodyTitleAreaEvents() override; + void init(); void toggleCustomTitle(bool visible); - const not_null _window; const std::unique_ptr _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 df9039f..550584e 100644 --- a/ui/platform/mac/ui_window_mac.mm +++ b/ui/platform/mac/ui_window_mac.mm @@ -7,10 +7,14 @@ #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 #include @@ -78,13 +82,42 @@ 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 { 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(); @@ -113,32 +146,87 @@ WindowHelper::Private::Private(not_null owner) init(); } +WindowHelper::Private::~Private() { + [_observer release]; +} + int WindowHelper::Private::customTitleHeight() const { return _customTitleHeight; } -Fn WindowHelper::Private::toggleCustomTitleCallback() { - return [=](bool visible) { - _owner->toggleCustomTitle(visible); +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 cgReal = [NSEvent mouseLocation]; + const auto real = QPointF(cgReal.x, cgReal.y); + const auto cgFrame = [_nativeWindow frame]; + const auto frame = QRectF(cgFrame.origin.x, cgFrame.origin.y, cgFrame.size.width, cgFrame.size.height); + const auto border = QMarginsF{ 3., 3., 3., 3. }; + return frame.marginsRemoved(border).contains(real); +} + +void WindowHelper::Private::activateBeforeNativeMove() { + [_nativeWindow makeKeyAndOrderFront:_nativeWindow]; +} + +void WindowHelper::Private::close() { + [_nativeWindow close]; +} + +Fn WindowHelper::Private::toggleCustomTitleCallback() { + 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() { - 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 +277,17 @@ 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())) { + if (_title->shouldBeHidden()) { + toggleCustomTitle(false); + } init(); } @@ -211,44 +302,92 @@ void WindowHelper::setTitle(const QString &title) { if (_title) { _title->setText(title); } - _window->setWindowTitle( + window()->setWindowTitle( (!_title || _title->isHidden()) ? title : QString()); } 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; } _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::setupBodyTitleAreaEvents() { +#ifndef OS_OSX + 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; + })); +#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() { + _private->close(); +} + 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 +402,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/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; } diff --git a/ui/platform/ui_platform_window.cpp b/ui/platform/ui_platform_window.cpp new file mode 100644 index 0000000..b45cdf4 --- /dev/null +++ b/ui/platform/ui_platform_window.cpp @@ -0,0 +1,125 @@ +// 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/widgets/window.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::showFullScreen() { + _window->showFullScreen(); +} + +void BasicWindowHelper::showNormal() { + _window->showNormal(); +} + +void BasicWindowHelper::close() { + _window->close(); +} + +void BasicWindowHelper::setBodyTitleArea( + Fn testMethod) { + Expects(!_bodyTitleAreaTestMethod); + + if (!testMethod) { + 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( + static_cast(e.get())->pos()); + }; + if (e->type() == QEvent::MouseButtonDblClick) { + _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 + } else if (e->type() == QEvent::MouseMove) { + const auto mouseEvent = static_cast(e.get()); + 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 + } + }, body()->lifetime()); +} + +} // namespace Platform +} // namespace Ui diff --git a/ui/platform/ui_platform_window.h b/ui/platform/ui_platform_window.h index 4fff950..0844545 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,23 +15,58 @@ struct WindowTitle; namespace Ui { class RpWidget; +enum class WindowTitleHitTestFlag; +using WindowTitleHitTestFlags = base::flags; 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); virtual ~BasicWindowHelper() = default; + [[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 void showFullScreen(); + virtual void showNormal(); + virtual void close(); + + void setBodyTitleArea(Fn testMethod); + +protected: + [[nodiscard]] not_null window() const { + return _window; + } + [[nodiscard]] WindowTitleHitTestFlags bodyTitleAreaHit( + QPoint point) const { + return _bodyTitleAreaTestMethod + ? _bodyTitleAreaTestMethod(point) + : WindowTitleHitTestFlag(); + } + +private: + virtual void setupBodyTitleAreaEvents(); + + const not_null _window; + Fn _bodyTitleAreaTestMethod; + bool _mousePressed = false; + }; -[[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_title_win.cpp b/ui/platform/win/ui_window_title_win.cpp index d08e332..c28cf16 100644 --- a/ui/platform/win/ui_window_title_win.cpp +++ b/ui/platform/win/ui_window_title_win.cpp @@ -19,39 +19,60 @@ 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, + const style::WindowTitle &st, + Fn maximize) +: _st(&st) +, _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 +80,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 +96,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 +131,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 +183,45 @@ void TitleWidget::updateButtonsState() { _close->setIconOverride(close, closeOver); } +TitleWidget::TitleWidget(not_null parent) +: RpWidget(parent) +, _controls(this, st::defaultWindowTitle) +, _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..6515065 100644 --- a/ui/platform/win/ui_window_title_win.h +++ b/ui/platform/win/ui_window_title_win.h @@ -38,6 +38,41 @@ enum class HitTestResult { TopLeft, }; +class TitleControls final { +public: + TitleControls( + not_null parent, + const style::WindowTitle &st, + 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 +87,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 diff --git a/ui/platform/win/ui_window_win.cpp b/ui/platform/win/ui_window_win.cpp index 934edd6..5e2652b 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,43 @@ 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::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([=] { _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 +181,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 +202,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 +283,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,7 +326,7 @@ 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; @@ -338,7 +354,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 +373,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 +395,7 @@ bool WindowHelper::handleNativeEvent( } bool WindowHelper::fixedSize() const { - return _window->minimumSize() == _window->maximumSize(); + return window()->minimumSize() == window()->maximumSize(); } void WindowHelper::updateMargins() { @@ -442,16 +458,19 @@ void WindowHelper::updateMargins() { _marginsDelta = QMargins(); } + if (_isFullScreen) { + margins = QMargins(); + } 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 +537,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..c74eb0b 100644 --- a/ui/platform/win/ui_window_win.h +++ b/ui/platform/win/ui_window_win.h @@ -27,6 +27,8 @@ 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; @@ -45,7 +47,6 @@ private: static not_null GetNativeFilter(); - const not_null _window; const HWND _handle = nullptr; const not_null _title; const not_null _body; @@ -53,6 +54,7 @@ private: bool _updatingMargins = false; QMargins _marginsDelta; HMENU _menu = nullptr; + bool _isFullScreen = false; }; diff --git a/ui/widgets/widgets.style b/ui/widgets/widgets.style index e31cf17..e69bcf8 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 { diff --git a/ui/widgets/window.cpp b/ui/widgets/window.cpp index 113fb7f..a105875 100644 --- a/ui/widgets/window.cpp +++ b/ui/widgets/window.cpp @@ -13,55 +13,56 @@ 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::showFullScreen() { + _helper->showFullScreen(); +} + +void Window::showNormal() { + _helper->showNormal(); +} + +void Window::close() { + _helper->close(); +} + +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..22e98ff 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,6 +42,10 @@ public: void setMinimumSize(QSize size); void setFixedSize(QSize size); void setGeometry(QRect rect); + void showFullScreen(); + void showNormal(); + void close(); + void setBodyTitleArea(Fn testMethod); private: const std::unique_ptr _helper;