Support custom semi-native title buttons on Windows.

This commit is contained in:
John Preston 2023-02-23 17:06:12 +04:00
parent a110c6ea53
commit 2aa929dc91
4 changed files with 210 additions and 88 deletions

View file

@ -104,18 +104,20 @@ void AbstractButton::setPointerCursor(bool enablePointerCursor) {
} }
void AbstractButton::setOver(bool over, StateChangeSource source) { void AbstractButton::setOver(bool over, StateChangeSource source) {
if (over && !(_state & StateFlag::Over)) { if (over == isOver()) {
auto was = _state; return;
}
const auto was = _state;
if (over) {
_state |= StateFlag::Over; _state |= StateFlag::Over;
Integration::Instance().registerLeaveSubscription(this); Integration::Instance().registerLeaveSubscription(this);
onStateChanged(was, source); } else {
} else if (!over && (_state & StateFlag::Over)) {
auto was = _state;
_state &= ~State(StateFlag::Over); _state &= ~State(StateFlag::Over);
Integration::Instance().unregisterLeaveSubscription(this); Integration::Instance().unregisterLeaveSubscription(this);
onStateChanged(was, source);
} }
onStateChanged(was, source);
updateCursor(); updateCursor();
update();
} }
bool AbstractButton::setDown( bool AbstractButton::setDown(
@ -149,8 +151,11 @@ bool AbstractButton::setDown(
} }
void AbstractButton::updateCursor() { void AbstractButton::updateCursor() {
auto pointerCursor = _enablePointerCursor && (_state & StateFlag::Over); const auto pointerCursor = _enablePointerCursor && isOver();
setCursor(pointerCursor ? style::cur_pointer : style::cur_default); if (_pointerCursor != pointerCursor) {
_pointerCursor = pointerCursor;
setCursor(_pointerCursor ? style::cur_pointer : style::cur_default);
}
} }
void AbstractButton::setDisabled(bool disabled) { void AbstractButton::setDisabled(bool disabled) {

View file

@ -16,22 +16,29 @@ class AbstractButton : public RpWidget {
public: public:
AbstractButton(QWidget *parent); AbstractButton(QWidget *parent);
Qt::KeyboardModifiers clickModifiers() const { [[nodiscard]] Qt::KeyboardModifiers clickModifiers() const {
return _modifiers; return _modifiers;
} }
void setDisabled(bool disabled = true); void setDisabled(bool disabled = true);
virtual void clearState(); virtual void clearState();
bool isOver() const { [[nodiscard]] bool isOver() const {
return _state & StateFlag::Over; return _state & StateFlag::Over;
} }
bool isDown() const { [[nodiscard]] bool isDown() const {
return _state & StateFlag::Down; return _state & StateFlag::Down;
} }
bool isDisabled() const { [[nodiscard]] bool isDisabled() const {
return _state & StateFlag::Disabled; return _state & StateFlag::Disabled;
} }
void setSynteticOver(bool over) {
setOver(over, StateChangeSource::ByPress);
}
void setSynteticDown(bool down, Qt::MouseButton button = Qt::LeftButton) {
setDown(down, StateChangeSource::ByPress, {}, button);
}
void setPointerCursor(bool enablePointerCursor); void setPointerCursor(bool enablePointerCursor);
void setAcceptBoth(bool acceptBoth = true); void setAcceptBoth(bool acceptBoth = true);
@ -95,9 +102,10 @@ private:
State _state = StateFlag::None; State _state = StateFlag::None;
bool _acceptBoth = false;
Qt::KeyboardModifiers _modifiers; Qt::KeyboardModifiers _modifiers;
bool _enablePointerCursor = true; bool _enablePointerCursor = true;
bool _pointerCursor = false;
bool _acceptBoth = false;
Fn<void()> _clickedCallback; Fn<void()> _clickedCallback;

View file

@ -65,30 +65,94 @@ void SetupSemiNativeSystemButtons(
}, lifetime); }, lifetime);
} }
class TitleControls::Button final : public IconButton { object_ptr<AbstractButton> IconTitleButtons::create(
public: not_null<QWidget*> parent,
using IconButton::IconButton; TitleControl control,
const style::WindowTitle &st) {
const auto make = [&](
QPointer<IconButton> &my,
const style::IconButton &st) {
Expects(!my);
void setOver(bool over) { auto result = object_ptr<IconButton>(parent, st);
IconButton::setOver(over, StateChangeSource::ByPress); my = result.data();
} return result;
void setDown(bool down) {
IconButton::setDown(
down,
StateChangeSource::ByPress,
{},
Qt::LeftButton);
}
}; };
switch (control) {
case TitleControl::Minimize:
return make(_minimize, st.minimize);
case TitleControl::Maximize:
return make(_maximizeRestore, st.maximize);
case TitleControl::Close:
return make(_close, st.close);
}
Unexpected("Control in IconTitleButtons::create.");
}
void IconTitleButtons::updateState(
bool active,
bool maximized,
const style::WindowTitle &st) {
if (_minimize) {
const auto minimize = active
? &st.minimizeIconActive
: &st.minimize.icon;
const auto minimizeOver = active
? &st.minimizeIconActiveOver
: &st.minimize.iconOver;
_minimize->setIconOverride(minimize, minimizeOver);
}
if (_maximizeRestore) {
if (maximized) {
const auto restore = active
? &st.restoreIconActive
: &st.restoreIcon;
const auto restoreOver = active
? &st.restoreIconActiveOver
: &st.restoreIconOver;
_maximizeRestore->setIconOverride(restore, restoreOver);
} else {
const auto maximize = active
? &st.maximizeIconActive
: &st.maximize.icon;
const auto maximizeOver = active
? &st.maximizeIconActiveOver
: &st.maximize.iconOver;
_maximizeRestore->setIconOverride(maximize, maximizeOver);
}
}
if (_close) {
const auto close = active
? &st.closeIconActive
: &st.close.icon;
const auto closeOver = active
? &st.closeIconActiveOver
: &st.close.iconOver;
_close->setIconOverride(close, closeOver);
}
}
TitleControls::TitleControls( TitleControls::TitleControls(
not_null<RpWidget*> parent, not_null<RpWidget*> parent,
const style::WindowTitle &st, const style::WindowTitle &st,
Fn<void(bool maximized)> maximize) Fn<void(bool maximized)> maximize)
: TitleControls(
parent,
st,
std::make_unique<IconTitleButtons>(),
std::move(maximize)) {
}
TitleControls::TitleControls(
not_null<RpWidget*> parent,
const style::WindowTitle &st,
std::unique_ptr<AbstractTitleButtons> buttons,
Fn<void(bool maximized)> maximize)
: _st(&st) : _st(&st)
, _minimize(parent, _st->minimize) , _buttons(std::move(buttons))
, _maximizeRestore(parent, _st->maximize) , _minimize(_buttons->create(parent, Control::Minimize, st))
, _close(parent, _st->close) , _maximizeRestore(_buttons->create(parent, Control::Maximize, st))
, _close(_buttons->create(parent, Control::Close, st))
, _maximizedState(parent->windowState() , _maximizedState(parent->windowState()
& (Qt::WindowMaximized | Qt::WindowFullScreen)) & (Qt::WindowMaximized | Qt::WindowFullScreen))
, _activeState(parent->isActiveWindow()) { , _activeState(parent->isActiveWindow()) {
@ -216,7 +280,9 @@ void TitleControls::raise() {
} }
HitTestResult TitleControls::hitTest(QPoint point, int padding) const { HitTestResult TitleControls::hitTest(QPoint point, int padding) const {
const auto test = [&](const object_ptr<Button> &button, bool close) { const auto test = [&](
const object_ptr<AbstractButton> &button,
bool close) {
return button && button->geometry().marginsAdded( return button && button->geometry().marginsAdded(
{ close ? padding : 0, padding, close ? padding : 0, 0 } { close ? padding : 0, padding, close ? padding : 0, 0 }
).contains(point); ).contains(point);
@ -233,23 +299,29 @@ HitTestResult TitleControls::hitTest(QPoint point, int padding) const {
void TitleControls::buttonOver(HitTestResult testResult) { void TitleControls::buttonOver(HitTestResult testResult) {
const auto update = [&]( const auto update = [&](
const object_ptr<Button> &button, const object_ptr<AbstractButton> &button,
HitTestResult buttonTestResult) { HitTestResult buttonTestResult,
Control control) {
const auto over = (testResult == buttonTestResult);
if (const auto raw = button.data()) { if (const auto raw = button.data()) {
raw->setOver(testResult == buttonTestResult); raw->setSynteticOver(over);
} }
_buttons->notifySynteticOver(control, over);
}; };
update(_minimize, HitTestResult::Minimize); update(_minimize, HitTestResult::Minimize, Control::Minimize);
update(_maximizeRestore, HitTestResult::MaximizeRestore); update(
update(_close, HitTestResult::Close); _maximizeRestore,
HitTestResult::MaximizeRestore,
Control::Maximize);
update(_close, HitTestResult::Close, Control::Close);
} }
void TitleControls::buttonDown(HitTestResult testResult) { void TitleControls::buttonDown(HitTestResult testResult) {
const auto update = [&]( const auto update = [&](
const object_ptr<Button> &button, const object_ptr<AbstractButton> &button,
HitTestResult buttonTestResult) { HitTestResult buttonTestResult) {
if (const auto raw = button.data()) { if (const auto raw = button.data()) {
raw->setDown(testResult == buttonTestResult); raw->setSynteticDown(testResult == buttonTestResult);
} }
}; };
update(_minimize, HitTestResult::Minimize); update(_minimize, HitTestResult::Minimize);
@ -257,7 +329,7 @@ void TitleControls::buttonDown(HitTestResult testResult) {
update(_close, HitTestResult::Close); update(_close, HitTestResult::Close);
} }
TitleControls::Button *TitleControls::controlWidget(Control control) const { AbstractButton *TitleControls::controlWidget(Control control) const {
switch (control) { switch (control) {
case Control::Minimize: return _minimize; case Control::Minimize: return _minimize;
case Control::Maximize: return _maximizeRestore; case Control::Maximize: return _maximizeRestore;
@ -370,37 +442,7 @@ void TitleControls::handleWindowStateChanged(Qt::WindowState state) {
} }
void TitleControls::updateButtonsState() { void TitleControls::updateButtonsState() {
const auto minimize = _activeState _buttons->updateState(_activeState, _maximizedState, *_st);
? &_st->minimizeIconActive
: &_st->minimize.icon;
const auto minimizeOver = _activeState
? &_st->minimizeIconActiveOver
: &_st->minimize.iconOver;
_minimize->setIconOverride(minimize, minimizeOver);
if (_maximizedState) {
const auto restore = _activeState
? &_st->restoreIconActive
: &_st->restoreIcon;
const auto restoreOver = _activeState
? &_st->restoreIconActiveOver
: &_st->restoreIconOver;
_maximizeRestore->setIconOverride(restore, restoreOver);
} else {
const auto maximize = _activeState
? &_st->maximizeIconActive
: &_st->maximize.icon;
const auto maximizeOver = _activeState
? &_st->maximizeIconActiveOver
: &_st->maximize.iconOver;
_maximizeRestore->setIconOverride(maximize, maximizeOver);
}
const auto close = _activeState
? &_st->closeIconActive
: &_st->close.icon;
const auto closeOver = _activeState
? &_st->closeIconActiveOver
: &_st->close.iconOver;
_close->setIconOverride(close, closeOver);
} }
DefaultTitleWidget::DefaultTitleWidget(not_null<RpWidget*> parent) DefaultTitleWidget::DefaultTitleWidget(not_null<RpWidget*> parent)
@ -479,17 +521,34 @@ SeparateTitleControls::SeparateTitleControls(
, controls(&wrap, st, std::move(maximize)) { , controls(&wrap, st, std::move(maximize)) {
} }
SeparateTitleControls::SeparateTitleControls(
QWidget *parent,
const style::WindowTitle &st,
std::unique_ptr<AbstractTitleButtons> buttons,
Fn<void(bool maximized)> maximize)
: wrap(parent)
, controls(&wrap, st, std::move(buttons), std::move(maximize)) {
}
std::unique_ptr<SeparateTitleControls> SetupSeparateTitleControls( std::unique_ptr<SeparateTitleControls> SetupSeparateTitleControls(
not_null<RpWindow*> window, not_null<RpWindow*> window,
const style::WindowTitle &st, const style::WindowTitle &st,
Fn<void(bool maximized)> maximize, Fn<void(bool maximized)> maximize,
rpl::producer<int> controlsTop) { rpl::producer<int> controlsTop) {
auto result = std::make_unique<SeparateTitleControls>( return SetupSeparateTitleControls(
window,
std::make_unique<SeparateTitleControls>(
window->body(), window->body(),
st, st,
std::move(maximize)); std::move(maximize)),
std::move(controlsTop));
}
const auto raw = result.get(); std::unique_ptr<SeparateTitleControls> SetupSeparateTitleControls(
not_null<RpWindow*> window,
std::unique_ptr<SeparateTitleControls> created,
rpl::producer<int> controlsTop) {
const auto raw = created.get();
auto &lifetime = raw->wrap.lifetime(); auto &lifetime = raw->wrap.lifetime();
rpl::combine( rpl::combine(
window->body()->widthValue(), window->body()->widthValue(),
@ -516,7 +575,7 @@ std::unique_ptr<SeparateTitleControls> SetupSeparateTitleControls(
SetupSemiNativeSystemButtons(&raw->controls, window, lifetime); SetupSemiNativeSystemButtons(&raw->controls, window, lifetime);
return result; return created;
} }
} // namespace Platform } // namespace Platform

View file

@ -19,6 +19,7 @@ struct WindowTitle;
namespace Ui { namespace Ui {
class IconButton; class IconButton;
class AbstractButton;
class PlainShadow; class PlainShadow;
class RpWindow; class RpWindow;
@ -55,12 +56,57 @@ void SetupSemiNativeSystemButtons(
rpl::lifetime &lifetime, rpl::lifetime &lifetime,
Fn<bool()> filter = nullptr); Fn<bool()> filter = nullptr);
enum class TitleControl {
Unknown,
Minimize,
Maximize,
Close,
};
class AbstractTitleButtons {
public:
[[nodiscard]] virtual object_ptr<AbstractButton> create(
not_null<QWidget*> parent,
TitleControl control,
const style::WindowTitle &st) = 0;
virtual void updateState(
bool active,
bool maximized,
const style::WindowTitle &st) = 0;
virtual void notifySynteticOver(TitleControl control, bool over) = 0;
};
class IconTitleButtons final : public AbstractTitleButtons {
public:
object_ptr<AbstractButton> create(
not_null<QWidget*> parent,
TitleControl control,
const style::WindowTitle &st) override;
void updateState(
bool active,
bool maximized,
const style::WindowTitle &st) override;
void notifySynteticOver(TitleControl control, bool over) override {
}
private:
QPointer<IconButton> _minimize;
QPointer<IconButton> _maximizeRestore;
QPointer<IconButton> _close;
};
class TitleControls final { class TitleControls final {
public: public:
TitleControls( TitleControls(
not_null<RpWidget*> parent, not_null<RpWidget*> parent,
const style::WindowTitle &st, const style::WindowTitle &st,
Fn<void(bool maximized)> maximize = nullptr); Fn<void(bool maximized)> maximize = nullptr);
TitleControls(
not_null<RpWidget*> parent,
const style::WindowTitle &st,
std::unique_ptr<AbstractTitleButtons> buttons,
Fn<void(bool maximized)> maximize = nullptr);
void setStyle(const style::WindowTitle &st); void setStyle(const style::WindowTitle &st);
[[nodiscard]] not_null<const style::WindowTitle*> st() const; [[nodiscard]] not_null<const style::WindowTitle*> st() const;
@ -73,24 +119,16 @@ public:
void buttonOver(HitTestResult testResult); void buttonOver(HitTestResult testResult);
void buttonDown(HitTestResult testResult); void buttonDown(HitTestResult testResult);
enum class Control { using Control = TitleControl;
Unknown,
Minimize,
Maximize,
Close,
};
struct Layout { struct Layout {
std::vector<Control> left; std::vector<Control> left;
std::vector<Control> right; std::vector<Control> right;
}; };
private: private:
class Button;
[[nodiscard]] not_null<RpWidget*> parent() const; [[nodiscard]] not_null<RpWidget*> parent() const;
[[nodiscard]] not_null<QWidget*> window() const; [[nodiscard]] not_null<QWidget*> window() const;
[[nodiscard]] Button *controlWidget(Control control) const; [[nodiscard]] AbstractButton *controlWidget(Control control) const;
void init(Fn<void(bool maximized)> maximize); void init(Fn<void(bool maximized)> maximize);
void subscribeToStateChanges(); void subscribeToStateChanges();
@ -102,10 +140,11 @@ private:
void handleWindowStateChanged(Qt::WindowState state = Qt::WindowNoState); void handleWindowStateChanged(Qt::WindowState state = Qt::WindowNoState);
not_null<const style::WindowTitle*> _st; not_null<const style::WindowTitle*> _st;
const std::unique_ptr<AbstractTitleButtons> _buttons;
object_ptr<Button> _minimize; object_ptr<AbstractButton> _minimize;
object_ptr<Button> _maximizeRestore; object_ptr<AbstractButton> _maximizeRestore;
object_ptr<Button> _close; object_ptr<AbstractButton> _close;
bool _maximizedState = false; bool _maximizedState = false;
bool _activeState = false; bool _activeState = false;
@ -143,6 +182,11 @@ struct SeparateTitleControls {
QWidget *parent, QWidget *parent,
const style::WindowTitle &st, const style::WindowTitle &st,
Fn<void(bool maximized)> maximize); Fn<void(bool maximized)> maximize);
SeparateTitleControls(
QWidget *parent,
const style::WindowTitle &st,
std::unique_ptr<AbstractTitleButtons> buttons,
Fn<void(bool maximized)> maximize);
RpWidget wrap; RpWidget wrap;
TitleControls controls; TitleControls controls;
@ -155,5 +199,11 @@ struct SeparateTitleControls {
rpl::producer<int> controlsTop = nullptr) rpl::producer<int> controlsTop = nullptr)
-> std::unique_ptr<SeparateTitleControls>; -> std::unique_ptr<SeparateTitleControls>;
[[nodiscard]] auto SetupSeparateTitleControls(
not_null<RpWindow*> window,
std::unique_ptr<SeparateTitleControls> created,
rpl::producer<int> controlsTop = nullptr)
-> std::unique_ptr<SeparateTitleControls>;
} // namespace Platform } // namespace Platform
} // namespace Ui } // namespace Ui