Support Windows 11 snap layouts in RpWindow title controls.
This commit is contained in:
parent
e8f4f02131
commit
d7e47aa8a6
8 changed files with 288 additions and 70 deletions
|
|
@ -26,7 +26,9 @@ AbstractButton::AbstractButton(QWidget *parent) : RpWidget(parent) {
|
|||
}
|
||||
|
||||
void AbstractButton::leaveEventHook(QEvent *e) {
|
||||
if (_state & StateFlag::Down) return;
|
||||
if (_state & StateFlag::Down) {
|
||||
return;
|
||||
}
|
||||
|
||||
setOver(false, StateChangeSource::ByHover);
|
||||
return TWidget::leaveEventHook(e);
|
||||
|
|
@ -48,12 +50,13 @@ void AbstractButton::checkIfOver(QPoint localPos) {
|
|||
|
||||
void AbstractButton::mousePressEvent(QMouseEvent *e) {
|
||||
checkIfOver(e->pos());
|
||||
if (_acceptBoth || (e->buttons() & Qt::LeftButton)) {
|
||||
if ((_state & StateFlag::Over) && !(_state & StateFlag::Down)) {
|
||||
auto was = _state;
|
||||
_state |= StateFlag::Down;
|
||||
onStateChanged(was, StateChangeSource::ByPress);
|
||||
|
||||
if (_state & StateFlag::Over) {
|
||||
const auto set = setDown(
|
||||
true,
|
||||
StateChangeSource::ByPress,
|
||||
e->modifiers(),
|
||||
e->button());
|
||||
if (set) {
|
||||
e->accept();
|
||||
}
|
||||
}
|
||||
|
|
@ -68,21 +71,13 @@ void AbstractButton::mouseMoveEvent(QMouseEvent *e) {
|
|||
}
|
||||
|
||||
void AbstractButton::mouseReleaseEvent(QMouseEvent *e) {
|
||||
if (_state & StateFlag::Down) {
|
||||
const auto was = _state;
|
||||
_state &= ~State(StateFlag::Down);
|
||||
|
||||
const auto weak = MakeWeak(this);
|
||||
onStateChanged(was, StateChangeSource::ByPress);
|
||||
if (!weak) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (was & StateFlag::Over) {
|
||||
clicked(e->modifiers(), e->button());
|
||||
} else {
|
||||
setOver(false, StateChangeSource::ByHover);
|
||||
}
|
||||
const auto set = setDown(
|
||||
false,
|
||||
StateChangeSource::ByPress,
|
||||
e->modifiers(),
|
||||
e->button());
|
||||
if (set) {
|
||||
e->accept();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -123,6 +118,36 @@ void AbstractButton::setOver(bool over, StateChangeSource source) {
|
|||
updateCursor();
|
||||
}
|
||||
|
||||
bool AbstractButton::setDown(
|
||||
bool down,
|
||||
StateChangeSource source,
|
||||
Qt::KeyboardModifiers modifiers,
|
||||
Qt::MouseButton button) {
|
||||
if (down
|
||||
&& !(_state & StateFlag::Down)
|
||||
&& (_acceptBoth || button == Qt::LeftButton)) {
|
||||
auto was = _state;
|
||||
_state |= StateFlag::Down;
|
||||
onStateChanged(was, source);
|
||||
return true;
|
||||
} else if (!down && (_state & StateFlag::Down)) {
|
||||
const auto was = _state;
|
||||
_state &= ~State(StateFlag::Down);
|
||||
|
||||
const auto weak = MakeWeak(this);
|
||||
onStateChanged(was, source);
|
||||
if (weak) {
|
||||
if (was & StateFlag::Over) {
|
||||
clicked(modifiers, button);
|
||||
} else {
|
||||
setOver(false, source);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void AbstractButton::updateCursor() {
|
||||
auto pointerCursor = _enablePointerCursor && (_state & StateFlag::Over);
|
||||
setCursor(pointerCursor ? style::cur_pointer : style::cur_default);
|
||||
|
|
|
|||
|
|
@ -80,6 +80,11 @@ protected:
|
|||
ByHover = 0x02,
|
||||
};
|
||||
void setOver(bool over, StateChangeSource source = StateChangeSource::ByUser);
|
||||
bool setDown(
|
||||
bool down,
|
||||
StateChangeSource source,
|
||||
Qt::KeyboardModifiers modifiers,
|
||||
Qt::MouseButton button);
|
||||
|
||||
virtual void onStateChanged(State was, StateChangeSource source) {
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,22 @@ void RemoveDuplicates(std::vector<T> &v) {
|
|||
|
||||
} // namespace
|
||||
|
||||
class TitleControls::Button final : public IconButton {
|
||||
public:
|
||||
using IconButton::IconButton;
|
||||
|
||||
void setOver(bool over) {
|
||||
IconButton::setOver(over, StateChangeSource::ByPress);
|
||||
}
|
||||
void setDown(bool down) {
|
||||
IconButton::setDown(
|
||||
down,
|
||||
StateChangeSource::ByPress,
|
||||
{},
|
||||
Qt::LeftButton);
|
||||
}
|
||||
};
|
||||
|
||||
TitleControls::TitleControls(
|
||||
not_null<RpWidget*> parent,
|
||||
const style::WindowTitle &st,
|
||||
|
|
@ -172,7 +188,47 @@ void TitleControls::raise() {
|
|||
_close->raise();
|
||||
}
|
||||
|
||||
Ui::IconButton *TitleControls::controlWidget(Control control) const {
|
||||
HitTestResult TitleControls::hitTest(QPoint point) const {
|
||||
const auto test = [&](const object_ptr<Button> &button) {
|
||||
return button && button->geometry().contains(point);
|
||||
};
|
||||
if (test(_minimize)) {
|
||||
return HitTestResult::Minimize;
|
||||
} else if (test(_maximizeRestore)) {
|
||||
return HitTestResult::MaximizeRestore;
|
||||
} else if (test(_close)) {
|
||||
return HitTestResult::Close;
|
||||
}
|
||||
return HitTestResult::None;
|
||||
}
|
||||
|
||||
void TitleControls::buttonOver(HitTestResult testResult) {
|
||||
const auto update = [&](
|
||||
const object_ptr<Button> &button,
|
||||
HitTestResult buttonTestResult) {
|
||||
if (const auto raw = button.data()) {
|
||||
raw->setOver(testResult == buttonTestResult);
|
||||
}
|
||||
};
|
||||
update(_minimize, HitTestResult::Minimize);
|
||||
update(_maximizeRestore, HitTestResult::MaximizeRestore);
|
||||
update(_close, HitTestResult::Close);
|
||||
}
|
||||
|
||||
void TitleControls::buttonDown(HitTestResult testResult, bool down) {
|
||||
const auto update = [&](
|
||||
const object_ptr<Button> &button,
|
||||
HitTestResult buttonTestResult) {
|
||||
if (const auto raw = button.data()) {
|
||||
raw->setDown(testResult == buttonTestResult && down);
|
||||
}
|
||||
};
|
||||
update(_minimize, HitTestResult::Minimize);
|
||||
update(_maximizeRestore, HitTestResult::MaximizeRestore);
|
||||
update(_close, HitTestResult::Close);
|
||||
}
|
||||
|
||||
TitleControls::Button *TitleControls::controlWidget(Control control) const {
|
||||
switch (control) {
|
||||
case Control::Minimize: return _minimize;
|
||||
case Control::Maximize: return _maximizeRestore;
|
||||
|
|
|
|||
|
|
@ -23,6 +23,23 @@ class PlainShadow;
|
|||
|
||||
namespace Platform {
|
||||
|
||||
enum class HitTestResult {
|
||||
None = 0,
|
||||
Client,
|
||||
Minimize,
|
||||
MaximizeRestore,
|
||||
Close,
|
||||
Caption,
|
||||
Top,
|
||||
TopRight,
|
||||
Right,
|
||||
BottomRight,
|
||||
Bottom,
|
||||
BottomLeft,
|
||||
Left,
|
||||
TopLeft,
|
||||
};
|
||||
|
||||
class TitleControls final {
|
||||
public:
|
||||
TitleControls(
|
||||
|
|
@ -36,6 +53,11 @@ public:
|
|||
void setResizeEnabled(bool enabled);
|
||||
void raise();
|
||||
|
||||
[[nodiscard]] HitTestResult hitTest(QPoint point) const;
|
||||
|
||||
void buttonOver(HitTestResult testResult);
|
||||
void buttonDown(HitTestResult testResult, bool down);
|
||||
|
||||
enum class Control {
|
||||
Unknown,
|
||||
Minimize,
|
||||
|
|
@ -49,9 +71,11 @@ public:
|
|||
};
|
||||
|
||||
private:
|
||||
class Button;
|
||||
|
||||
[[nodiscard]] not_null<RpWidget*> parent() const;
|
||||
[[nodiscard]] not_null<QWidget*> window() const;
|
||||
[[nodiscard]] Ui::IconButton *controlWidget(Control control) const;
|
||||
[[nodiscard]] Button *controlWidget(Control control) const;
|
||||
|
||||
void init(Fn<void(bool maximized)> maximize);
|
||||
void subscribeToStateChanges();
|
||||
|
|
@ -64,9 +88,9 @@ private:
|
|||
|
||||
not_null<const style::WindowTitle*> _st;
|
||||
|
||||
object_ptr<Ui::IconButton> _minimize;
|
||||
object_ptr<Ui::IconButton> _maximizeRestore;
|
||||
object_ptr<Ui::IconButton> _close;
|
||||
object_ptr<Button> _minimize;
|
||||
object_ptr<Button> _maximizeRestore;
|
||||
object_ptr<Button> _close;
|
||||
|
||||
bool _maximizedState = false;
|
||||
bool _activeState = false;
|
||||
|
|
|
|||
|
|
@ -113,8 +113,9 @@ void TitleWidget::resizeEvent(QResizeEvent *e) {
|
|||
}
|
||||
|
||||
HitTestResult TitleWidget::hitTest(QPoint point) const {
|
||||
if (_controls.geometry().contains(point)) {
|
||||
return HitTestResult::SysButton;
|
||||
const auto titleResult = _controls.hitTest(point);
|
||||
if (titleResult != HitTestResult::None) {
|
||||
return titleResult;
|
||||
} else if (rect().contains(point)) {
|
||||
return HitTestResult::Caption;
|
||||
}
|
||||
|
|
@ -125,6 +126,14 @@ 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;
|
||||
|
|
|
|||
|
|
@ -26,21 +26,6 @@ class PlainShadow;
|
|||
|
||||
namespace Platform {
|
||||
|
||||
enum class HitTestResult {
|
||||
None = 0,
|
||||
Client,
|
||||
SysButton,
|
||||
Caption,
|
||||
Top,
|
||||
TopRight,
|
||||
Right,
|
||||
BottomRight,
|
||||
Bottom,
|
||||
BottomLeft,
|
||||
Left,
|
||||
TopLeft,
|
||||
};
|
||||
|
||||
class TitleWidget : public RpWidget {
|
||||
public:
|
||||
explicit TitleWidget(not_null<RpWidget*> parent);
|
||||
|
|
@ -58,6 +43,9 @@ public:
|
|||
HWND handle,
|
||||
const WINDOWPLACEMENT &placement);
|
||||
|
||||
void sysButtonOver(HitTestResult testResult);
|
||||
void sysButtonDown(HitTestResult testResult, bool down);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
|
|
|
|||
|
|
@ -321,6 +321,10 @@ bool WindowHelper::handleNativeEvent(
|
|||
WPARAM wParam,
|
||||
LPARAM lParam,
|
||||
LRESULT *result) {
|
||||
if (handleSysButtonEvent(msg, wParam, lParam, result)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (msg) {
|
||||
|
||||
case WM_ACTIVATE: {
|
||||
|
|
@ -392,6 +396,14 @@ bool WindowHelper::handleNativeEvent(
|
|||
if (result) *result = 0;
|
||||
} return true;
|
||||
|
||||
case WM_NCRBUTTONUP: {
|
||||
if (_title->isHidden()) {
|
||||
return false;
|
||||
}
|
||||
SendMessage(_handle, WM_SYSCOMMAND, SC_MOUSEMENU, lParam);
|
||||
if (result) *result = 0;
|
||||
} return true;
|
||||
|
||||
case WM_NCACTIVATE: {
|
||||
if (_title->isHidden()) {
|
||||
return false;
|
||||
|
|
@ -483,33 +495,33 @@ bool WindowHelper::handleNativeEvent(
|
|||
const auto mapped = QPoint(
|
||||
p.x - r.left + _marginsDelta.left(),
|
||||
p.y - r.top + _marginsDelta.top());
|
||||
if (!window()->rect().contains(mapped)) {
|
||||
*result = HTTRANSPARENT;
|
||||
} else if (_title->isHidden()
|
||||
|| !_title->geometry().contains(mapped)) {
|
||||
*result = HTCLIENT;
|
||||
} else switch (_title->hitTest(_title->pos() + mapped)) {
|
||||
case HitTestResult::Client:
|
||||
case HitTestResult::SysButton: *result = HTCLIENT; break;
|
||||
case HitTestResult::Caption: *result = HTCAPTION; break;
|
||||
case HitTestResult::Top: *result = HTTOP; break;
|
||||
case HitTestResult::TopRight: *result = HTTOPRIGHT; break;
|
||||
case HitTestResult::Right: *result = HTRIGHT; break;
|
||||
case HitTestResult::BottomRight: *result = HTBOTTOMRIGHT; break;
|
||||
case HitTestResult::Bottom: *result = HTBOTTOM; break;
|
||||
case HitTestResult::BottomLeft: *result = HTBOTTOMLEFT; break;
|
||||
case HitTestResult::Left: *result = HTLEFT; break;
|
||||
case HitTestResult::TopLeft: *result = HTTOPLEFT; break;
|
||||
case HitTestResult::None:
|
||||
default: *result = HTTRANSPARENT; break;
|
||||
};
|
||||
} return true;
|
||||
|
||||
case WM_NCRBUTTONUP: {
|
||||
if (_title->isHidden()) {
|
||||
return false;
|
||||
*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)) {
|
||||
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;
|
||||
case HitTestResult::Right: return HTRIGHT;
|
||||
case HitTestResult::BottomRight: return HTBOTTOMRIGHT;
|
||||
case HitTestResult::Bottom: return HTBOTTOM;
|
||||
case HitTestResult::BottomLeft: return HTBOTTOMLEFT;
|
||||
case HitTestResult::Left: return HTLEFT;
|
||||
case HitTestResult::TopLeft: return HTTOPLEFT;
|
||||
case HitTestResult::None:
|
||||
default: return HTTRANSPARENT;
|
||||
};
|
||||
}();
|
||||
if (complexSysButtonProcessing()) {
|
||||
_title->sysButtonOver(sysButtonHitTest(*result));
|
||||
}
|
||||
SendMessage(_handle, WM_SYSCOMMAND, SC_MOUSEMENU, lParam);
|
||||
} return true;
|
||||
|
||||
case WM_SYSCOMMAND: {
|
||||
|
|
@ -559,6 +571,96 @@ bool WindowHelper::fixedSize() const {
|
|||
return window()->minimumSize() == window()->maximumSize();
|
||||
}
|
||||
|
||||
bool WindowHelper::handleSysButtonEvent(
|
||||
UINT msg,
|
||||
WPARAM wParam,
|
||||
LPARAM lParam,
|
||||
LRESULT *result) {
|
||||
if (!complexSysButtonProcessing()) {
|
||||
return false;
|
||||
}
|
||||
const auto testResult = LOWORD(wParam);
|
||||
const auto sysButtons = { HTMINBUTTON, HTMAXBUTTON, HTCLOSE };
|
||||
const auto overSysButton = ranges::contains(sysButtons, testResult);
|
||||
switch (msg) {
|
||||
case WM_NCLBUTTONDBLCLK:
|
||||
case WM_NCMBUTTONDBLCLK:
|
||||
case WM_NCRBUTTONDBLCLK:
|
||||
case WM_NCXBUTTONDBLCLK: {
|
||||
if (!overSysButton || fixedSize()) {
|
||||
return false;
|
||||
}
|
||||
// Ignore double clicks on system buttons.
|
||||
if (result) *result = 0;
|
||||
} return true;
|
||||
|
||||
case WM_NCLBUTTONDOWN:
|
||||
case WM_NCLBUTTONUP:
|
||||
_title->sysButtonDown(
|
||||
sysButtonHitTest(testResult),
|
||||
(msg == WM_NCLBUTTONDOWN));
|
||||
if (overSysButton) {
|
||||
if (result) *result = 0;
|
||||
}
|
||||
return overSysButton;
|
||||
case WM_NCMBUTTONDOWN:
|
||||
case WM_NCMBUTTONUP:
|
||||
case WM_NCRBUTTONDOWN:
|
||||
case WM_NCRBUTTONUP:
|
||||
case WM_NCXBUTTONDOWN:
|
||||
case WM_NCXBUTTONUP:
|
||||
if (!overSysButton) {
|
||||
return false;
|
||||
}
|
||||
if (result) *result = 0;
|
||||
return true;
|
||||
case WM_NCMOUSEHOVER:
|
||||
case WM_NCMOUSEMOVE:
|
||||
_title->sysButtonOver(sysButtonHitTest(testResult));
|
||||
if (overSysButton) {
|
||||
if (result) *result = 0;
|
||||
}
|
||||
return overSysButton;
|
||||
case WM_NCMOUSELEAVE:
|
||||
_title->sysButtonOver(HitTestResult::None);
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int WindowHelper::sysButtonHitTest(HitTestResult result) const {
|
||||
if (!complexSysButtonProcessing()) {
|
||||
return HTCLIENT;
|
||||
}
|
||||
switch (result) {
|
||||
case HitTestResult::Minimize: return HTMINBUTTON;
|
||||
case HitTestResult::MaximizeRestore: return HTMAXBUTTON;
|
||||
case HitTestResult::Close: return HTCLOSE;
|
||||
}
|
||||
return HTTRANSPARENT;
|
||||
}
|
||||
|
||||
HitTestResult WindowHelper::sysButtonHitTest(int result) const {
|
||||
if (!complexSysButtonProcessing()) {
|
||||
return HitTestResult::Client;
|
||||
}
|
||||
switch (result) {
|
||||
case HTMINBUTTON: return HitTestResult::Minimize;
|
||||
case HTMAXBUTTON: return HitTestResult::MaximizeRestore;
|
||||
case HTCLOSE: return HitTestResult::Close;
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ namespace Ui {
|
|||
namespace Platform {
|
||||
|
||||
class TitleWidget;
|
||||
enum class HitTestResult;
|
||||
|
||||
class WindowHelper final : public BasicWindowHelper {
|
||||
public:
|
||||
|
|
@ -49,7 +50,15 @@ private:
|
|||
WPARAM wParam,
|
||||
LPARAM lParam,
|
||||
LRESULT *result);
|
||||
[[nodiscard]] bool handleSysButtonEvent(
|
||||
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 titleHeight() const;
|
||||
static not_null<NativeFilter*> GetNativeFilter();
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue