Add Windows 11 snap layouts to custom TitleControls.

This commit is contained in:
John Preston 2022-01-18 18:37:35 +03:00
parent d5d2ccb467
commit 47aed59fe9
10 changed files with 200 additions and 76 deletions

View file

@ -33,6 +33,9 @@ BasicWindowHelper::BasicWindowHelper(not_null<RpWidget*> window)
_window->setWindowFlag(Qt::Window);
}
void BasicWindowHelper::initInWindow(not_null<RpWindow*> window) {
}
not_null<RpWidget*> BasicWindowHelper::body() {
return _window;
}
@ -49,6 +52,19 @@ rpl::producer<int> BasicWindowHelper::additionalContentPaddingValue() const {
return rpl::single(0);
}
auto BasicWindowHelper::hitTestRequests() const
-> rpl::producer<not_null<HitTestRequest*>> {
return rpl::never<not_null<HitTestRequest*>>();
}
rpl::producer<HitTestResult> BasicWindowHelper::systemButtonOver() const {
return rpl::never<HitTestResult>();
}
rpl::producer<HitTestResult> BasicWindowHelper::systemButtonDown() const {
return rpl::never<HitTestResult>();
}
void BasicWindowHelper::setTitle(const QString &title) {
_window->setWindowTitle(title);
}

View file

@ -15,11 +15,14 @@ struct WindowTitle;
namespace Ui {
class RpWidget;
class RpWindow;
enum class WindowTitleHitTestFlag;
using WindowTitleHitTestFlags = base::flags<WindowTitleHitTestFlag>;
namespace Platform {
struct HitTestRequest;
enum class HitTestResult;
class DefaultTitleWidget;
class BasicWindowHelper {
@ -27,11 +30,22 @@ public:
explicit BasicWindowHelper(not_null<RpWidget*> window);
virtual ~BasicWindowHelper() = default;
[[nodiscard]] not_null<RpWidget*> window() const {
return _window;
}
[[nodiscard]] virtual void initInWindow(not_null<RpWindow*> window);
[[nodiscard]] virtual not_null<RpWidget*> body();
[[nodiscard]] virtual QMargins frameMargins();
[[nodiscard]] virtual int additionalContentPadding() const;
[[nodiscard]] virtual auto additionalContentPaddingValue() const
-> rpl::producer<int>;
[[nodiscard]] virtual auto hitTestRequests() const
-> rpl::producer<not_null<HitTestRequest*>>;
[[nodiscard]] virtual auto systemButtonOver() const
-> rpl::producer<HitTestResult>;
[[nodiscard]] virtual auto systemButtonDown() const
-> rpl::producer<HitTestResult>;
virtual void setTitle(const QString &title);
virtual void setTitleStyle(const style::WindowTitle &st);
virtual void setNativeFrame(bool enabled);
@ -46,9 +60,6 @@ public:
void setBodyTitleArea(Fn<WindowTitleHitTestFlags(QPoint)> testMethod);
protected:
[[nodiscard]] not_null<RpWidget*> window() const {
return _window;
}
[[nodiscard]] WindowTitleHitTestFlags bodyTitleAreaHit(
QPoint point) const {
return _bodyTitleAreaTestMethod
@ -111,7 +122,7 @@ private:
return std::make_unique<DefaultWindowHelper>(window);
}
bool NativeWindowFrameSupported();
[[nodiscard]] bool NativeWindowFrameSupported();
} // namespace Platform
} // namespace Ui

View file

@ -15,6 +15,7 @@
#include "styles/palette.h"
#include "base/algorithm.h"
#include "base/event_filter.h"
#include "base/platform/base_platform_info.h"
#include <QtGui/QPainter>
#include <QtGui/QtEvents>
@ -36,6 +37,34 @@ void RemoveDuplicates(std::vector<T> &v) {
} // namespace
bool SemiNativeSystemButtonProcessing() {
return ::Platform::IsWindows11OrGreater();
}
void SetupSemiNativeSystemButtons(
not_null<TitleControls*> controls,
not_null<RpWindow*> window,
rpl::lifetime &lifetime,
Fn<bool()> filter) {
if (!SemiNativeSystemButtonProcessing()) {
return;
}
window->systemButtonOver(
) | rpl::filter([=](HitTestResult button) {
return !filter || filter() || (button == HitTestResult::None);
}) | rpl::start_with_next([=](HitTestResult button) {
controls->buttonOver(button);
}, lifetime);
window->systemButtonDown(
) | rpl::filter([=](HitTestResult button) {
return !filter || filter() || (button == HitTestResult::None);
}) | rpl::start_with_next([=](HitTestResult button) {
controls->buttonDown(button);
}, lifetime);
}
class TitleControls::Button final : public IconButton {
public:
using IconButton::IconButton;
@ -216,12 +245,12 @@ void TitleControls::buttonOver(HitTestResult testResult) {
update(_close, HitTestResult::Close);
}
void TitleControls::buttonDown(HitTestResult testResult, bool down) {
void TitleControls::buttonDown(HitTestResult testResult) {
const auto update = [&](
const object_ptr<Button> &button,
HitTestResult buttonTestResult) {
if (const auto raw = button.data()) {
raw->setDown(testResult == buttonTestResult && down);
raw->setDown(testResult == buttonTestResult);
}
};
update(_minimize, HitTestResult::Minimize);
@ -456,16 +485,30 @@ std::unique_ptr<SeparateTitleControls> SetupSeparateTitleControls(
st,
std::move(maximize));
const auto raw = result.get();
auto &lifetime = raw->wrap.lifetime();
rpl::combine(
window->body()->widthValue(),
window->additionalContentPaddingValue()
) | rpl::start_with_next([raw = result.get()](int width, int padding) {
) | rpl::start_with_next([=](int width, int padding) {
raw->wrap.setGeometry(
padding,
0,
width - 2 * padding,
raw->controls.geometry().height());
}, result->wrap.lifetime());
}, lifetime);
window->hitTestRequests(
) | rpl::start_with_next([=](not_null<HitTestRequest*> request) {
const auto origin = raw->wrap.pos();
const auto relative = request->point - origin;
const auto controlsResult = raw->controls.hitTest(relative);
if (controlsResult != HitTestResult::None) {
request->result = controlsResult;
}
}, lifetime);
SetupSemiNativeSystemButtons(&raw->controls, window, lifetime);
return result;
}

View file

@ -24,6 +24,8 @@ class RpWindow;
namespace Platform {
class TitleControls;
enum class HitTestResult {
None = 0,
Client,
@ -41,6 +43,18 @@ enum class HitTestResult {
TopLeft,
};
struct HitTestRequest {
QPoint point;
HitTestResult result = HitTestResult::Client;
};
[[nodiscard]] bool SemiNativeSystemButtonProcessing();
void SetupSemiNativeSystemButtons(
not_null<TitleControls*> controls,
not_null<RpWindow*> window,
rpl::lifetime &lifetime,
Fn<bool()> filter = nullptr);
class TitleControls final {
public:
TitleControls(
@ -57,7 +71,7 @@ public:
[[nodiscard]] HitTestResult hitTest(QPoint point) const;
void buttonOver(HitTestResult testResult);
void buttonDown(HitTestResult testResult, bool down);
void buttonDown(HitTestResult testResult);
enum class Control {
Unknown,

View file

@ -9,6 +9,7 @@
#include "ui/platform/win/ui_window_win.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/shadow.h"
#include "ui/widgets/rp_window.h"
#include "ui/ui_utility.h"
#include "base/platform/base_platform_info.h"
#include "base/platform/win/base_windows_safe_library.h"
@ -68,6 +69,19 @@ TitleWidget::TitleWidget(not_null<RpWidget*> parent)
}, lifetime());
}
void TitleWidget::initInWindow(not_null<RpWindow*> window) {
window->hitTestRequests(
) | rpl::filter([=](not_null<HitTestRequest*> request) {
return !isHidden() && geometry().contains(request->point);
}) | rpl::start_with_next([=](not_null<HitTestRequest*> request) {
request->result = hitTest(request->point);
}, lifetime());
SetupSemiNativeSystemButtons(&_controls, window, lifetime(), [=] {
return !isHidden() && (_controls.st()->height > 0);
});
}
TitleWidget::~TitleWidget() = default;
void TitleWidget::setText(const QString &text) {
@ -113,27 +127,19 @@ void TitleWidget::resizeEvent(QResizeEvent *e) {
}
HitTestResult TitleWidget::hitTest(QPoint point) const {
const auto titleResult = _controls.hitTest(point);
if (titleResult != HitTestResult::None) {
return titleResult;
} else if (rect().contains(point)) {
return HitTestResult::Caption;
}
return HitTestResult::None;
const auto origin = _paddingHelper
? _paddingHelper->controlsParent.pos()
: QPoint();
const auto controlsResult = _controls.hitTest(point - origin);
return (controlsResult != HitTestResult::None)
? controlsResult
: HitTestResult::Caption;
}
bool TitleWidget::additionalPaddingRequired() const {
return _paddingHelper && !isHidden();
}
void TitleWidget::sysButtonOver(HitTestResult testResult) {
_controls.buttonOver(testResult);
}
void TitleWidget::sysButtonDown(HitTestResult testResult, bool down) {
_controls.buttonDown(testResult, down);
}
void TitleWidget::refreshAdditionalPaddings() {
if (!additionalPaddingRequired()) {
return;

View file

@ -21,6 +21,7 @@ struct WindowTitle;
namespace Ui {
class RpWindow;
class IconButton;
class PlainShadow;
@ -31,10 +32,10 @@ public:
explicit TitleWidget(not_null<RpWidget*> parent);
~TitleWidget();
void initInWindow(not_null<RpWindow*> window);
void setText(const QString &text);
void setStyle(const style::WindowTitle &st);
[[nodiscard]] not_null<const style::WindowTitle*> st() const;
[[nodiscard]] HitTestResult hitTest(QPoint point) const;
void setResizeEnabled(bool enabled);
void refreshAdditionalPaddings();
@ -45,9 +46,6 @@ public:
[[nodiscard]] int additionalPadding() const;
[[nodiscard]] rpl::producer<int> additionalPaddingValue() const;
void sysButtonOver(HitTestResult testResult);
void sysButtonDown(HitTestResult testResult, bool down);
protected:
void paintEvent(QPaintEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
@ -57,6 +55,7 @@ protected:
private:
struct PaddingHelper;
[[nodiscard]] HitTestResult hitTest(QPoint point) const;
[[nodiscard]] bool additionalPaddingRequired() const;
void refreshGeometryWithWidth(int width);
void setAdditionalPadding(int padding);

View file

@ -8,6 +8,7 @@
#include "ui/inactive_press.h"
#include "ui/platform/win/ui_window_title_win.h"
#include "ui/widgets/rp_window.h"
#include "base/platform/base_platform_info.h"
#include "base/integration.h"
#include "base/debug_log.h"
@ -170,6 +171,10 @@ WindowHelper::~WindowHelper() {
GetNativeFilter()->unregisterWindow(_handle);
}
void WindowHelper::initInWindow(not_null<RpWindow*> window) {
_title->initInWindow(window);
}
not_null<RpWidget*> WindowHelper::body() {
return _body;
}
@ -280,6 +285,19 @@ void WindowHelper::showNormal() {
}
}
auto WindowHelper::hitTestRequests() const
-> rpl::producer<not_null<HitTestRequest*>> {
return _hitTestRequests.events();
}
rpl::producer<HitTestResult> WindowHelper::systemButtonOver() const {
return _systemButtonOver.events();
}
rpl::producer<HitTestResult> WindowHelper::systemButtonDown() const {
return _systemButtonDown.events();
}
void WindowHelper::init() {
_title->show();
GetNativeFilter()->registerWindow(_handle, this);
@ -341,7 +359,7 @@ bool WindowHelper::handleNativeEvent(
WPARAM wParam,
LPARAM lParam,
LRESULT *result) {
if (handleSysButtonEvent(msg, wParam, lParam, result)) {
if (handleSystemButtonEvent(msg, wParam, lParam, result)) {
return true;
}
@ -406,16 +424,6 @@ bool WindowHelper::handleNativeEvent(
return true;
}
case WM_NCLBUTTONDBLCLK:
case WM_NCMBUTTONDBLCLK:
case WM_NCRBUTTONDBLCLK:
case WM_NCXBUTTONDBLCLK: {
if (!fixedSize()) {
return false;
}
if (result) *result = 0;
} return true;
case WM_NCRBUTTONUP: {
if (_title->isHidden()) {
return false;
@ -518,14 +526,13 @@ bool WindowHelper::handleNativeEvent(
*result = [&] {
if (!window()->rect().contains(mapped)) {
return HTTRANSPARENT;
} else if (_title->isHidden()
|| !_title->geometry().contains(mapped)) {
return HTCLIENT;
} else switch (const auto test = _title->hitTest(mapped)) {
}
auto request = HitTestRequest{
.point = mapped,
};
_hitTestRequests.fire(&request);
switch (const auto result = request.result) {
case HitTestResult::Client: return HTCLIENT;
case HitTestResult::Minimize:
case HitTestResult::MaximizeRestore:
case HitTestResult::Close: return sysButtonHitTest(test);
case HitTestResult::Caption: return HTCAPTION;
case HitTestResult::Top: return HTTOP;
case HitTestResult::TopRight: return HTTOPRIGHT;
@ -535,13 +542,16 @@ bool WindowHelper::handleNativeEvent(
case HitTestResult::BottomLeft: return HTBOTTOMLEFT;
case HitTestResult::Left: return HTLEFT;
case HitTestResult::TopLeft: return HTTOPLEFT;
case HitTestResult::Minimize:
case HitTestResult::MaximizeRestore:
case HitTestResult::Close: return systemButtonHitTest(result);
case HitTestResult::None:
default: return HTTRANSPARENT;
default: return HTTRANSPARENT;
};
}();
if (complexSysButtonProcessing()) {
_title->sysButtonOver(sysButtonHitTest(*result));
}
_systemButtonOver.fire(systemButtonHitTest(*result));
} return true;
case WM_SYSCOMMAND: {
@ -591,12 +601,12 @@ bool WindowHelper::fixedSize() const {
return window()->minimumSize() == window()->maximumSize();
}
bool WindowHelper::handleSysButtonEvent(
bool WindowHelper::handleSystemButtonEvent(
UINT msg,
WPARAM wParam,
LPARAM lParam,
LRESULT *result) {
if (!complexSysButtonProcessing()) {
if (_title->isHidden()) {
return false;
}
const auto testResult = LOWORD(wParam);
@ -616,9 +626,9 @@ bool WindowHelper::handleSysButtonEvent(
case WM_NCLBUTTONDOWN:
case WM_NCLBUTTONUP:
_title->sysButtonDown(
sysButtonHitTest(testResult),
(msg == WM_NCLBUTTONDOWN));
_systemButtonDown.fire((msg == WM_NCLBUTTONDOWN)
? systemButtonHitTest(testResult)
: HitTestResult::None);
if (overSysButton) {
if (result) *result = 0;
}
@ -636,20 +646,20 @@ bool WindowHelper::handleSysButtonEvent(
return true;
case WM_NCMOUSEHOVER:
case WM_NCMOUSEMOVE:
_title->sysButtonOver(sysButtonHitTest(testResult));
_systemButtonOver.fire(systemButtonHitTest(testResult));
if (overSysButton) {
if (result) *result = 0;
}
return overSysButton;
case WM_NCMOUSELEAVE:
_title->sysButtonOver(HitTestResult::None);
_systemButtonOver.fire(HitTestResult::None);
return false;
}
return false;
}
int WindowHelper::sysButtonHitTest(HitTestResult result) const {
if (!complexSysButtonProcessing()) {
int WindowHelper::systemButtonHitTest(HitTestResult result) const {
if (!SemiNativeSystemButtonProcessing()) {
return HTCLIENT;
}
switch (result) {
@ -660,9 +670,9 @@ int WindowHelper::sysButtonHitTest(HitTestResult result) const {
return HTTRANSPARENT;
}
HitTestResult WindowHelper::sysButtonHitTest(int result) const {
if (!complexSysButtonProcessing()) {
return HitTestResult::Client;
HitTestResult WindowHelper::systemButtonHitTest(int result) const {
if (!SemiNativeSystemButtonProcessing()) {
return HitTestResult::None;
}
switch (result) {
case HTMINBUTTON: return HitTestResult::Minimize;
@ -672,15 +682,6 @@ HitTestResult WindowHelper::sysButtonHitTest(int result) const {
return HitTestResult::None;
}
bool WindowHelper::complexSysButtonProcessing() const {
if (_title->isHidden()) {
return false;
} else if (!::Platform::IsWindows11OrGreater()) {
return false;
}
return true;
}
int WindowHelper::titleHeight() const {
return _title->isHidden() ? 0 : _title->height();
}

View file

@ -15,6 +15,7 @@ namespace Ui {
namespace Platform {
class TitleWidget;
struct HitTestRequest;
enum class HitTestResult;
class WindowHelper final : public BasicWindowHelper {
@ -22,6 +23,7 @@ public:
explicit WindowHelper(not_null<RpWidget*> window);
~WindowHelper();
void initInWindow(not_null<RpWindow*> window) override;
not_null<RpWidget*> body() override;
QMargins frameMargins() override;
int additionalContentPadding() const override;
@ -35,6 +37,13 @@ public:
void showFullScreen() override;
void showNormal() override;
[[nodiscard]] auto hitTestRequests() const
-> rpl::producer<not_null<HitTestRequest*>> override;
[[nodiscard]] auto systemButtonOver() const
-> rpl::producer<HitTestResult> override;
[[nodiscard]] auto systemButtonDown() const
-> rpl::producer<HitTestResult> override;
private:
class NativeFilter;
friend class NativeFilter;
@ -53,15 +62,14 @@ private:
WPARAM wParam,
LPARAM lParam,
LRESULT *result);
[[nodiscard]] bool handleSysButtonEvent(
[[nodiscard]] bool handleSystemButtonEvent(
UINT msg,
WPARAM wParam,
LPARAM lParam,
LRESULT *result);
[[nodiscard]] bool fixedSize() const;
[[nodiscard]] bool complexSysButtonProcessing() const;
[[nodiscard]] int sysButtonHitTest(HitTestResult result) const;
[[nodiscard]] HitTestResult sysButtonHitTest(int result) const;
[[nodiscard]] int systemButtonHitTest(HitTestResult result) const;
[[nodiscard]] HitTestResult systemButtonHitTest(int result) const;
[[nodiscard]] int titleHeight() const;
static not_null<NativeFilter*> GetNativeFilter();
@ -69,10 +77,13 @@ private:
const HWND _handle = nullptr;
const not_null<TitleWidget*> _title;
const not_null<RpWidget*> _body;
rpl::event_stream<not_null<HitTestRequest*>> _hitTestRequests;
rpl::event_stream<HitTestResult> _systemButtonOver;
rpl::event_stream<HitTestResult> _systemButtonDown;
std::optional<WindowShadow> _shadow;
bool _updatingMargins = false;
QMargins _marginsDelta;
HMENU _menu = nullptr;
bool _updatingMargins = false;
bool _isFullScreen = false;
};

View file

@ -15,6 +15,7 @@ RpWindow::RpWindow(QWidget *parent)
, _helper(Platform::CreateWindowHelper(this)) {
Expects(_helper != nullptr);
_helper->initInWindow(this);
hide();
}
@ -40,6 +41,19 @@ rpl::producer<int> RpWindow::additionalContentPaddingValue() const {
return _helper->additionalContentPaddingValue();
}
auto RpWindow::hitTestRequests() const
-> rpl::producer<not_null<Platform::HitTestRequest*>> {
return _helper->hitTestRequests();
}
rpl::producer<Platform::HitTestResult> RpWindow::systemButtonOver() const {
return _helper->systemButtonOver();
}
rpl::producer<Platform::HitTestResult> RpWindow::systemButtonDown() const {
return _helper->systemButtonDown();
}
void RpWindow::setTitle(const QString &title) {
_helper->setTitle(title);
}

View file

@ -16,6 +16,8 @@ struct WindowTitle;
namespace Ui {
namespace Platform {
class BasicWindowHelper;
struct HitTestRequest;
enum class HitTestResult;
} // namespace Platform
enum class WindowTitleHitTestFlag {
@ -46,6 +48,13 @@ public:
[[nodiscard]] int additionalContentPadding() const;
[[nodiscard]] rpl::producer<int> additionalContentPaddingValue() const;
[[nodiscard]] auto hitTestRequests() const
-> rpl::producer<not_null<Platform::HitTestRequest*>>;
[[nodiscard]] auto systemButtonOver() const
-> rpl::producer<Platform::HitTestResult>;
[[nodiscard]] auto systemButtonDown() const
-> rpl::producer<Platform::HitTestResult>;
void setTitle(const QString &title);
void setTitleStyle(const style::WindowTitle &st);
void setNativeFrame(bool enabled);