569 lines
14 KiB
C++
569 lines
14 KiB
C++
// 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_title.h"
|
|
|
|
#include "ui/platform/ui_platform_utility.h"
|
|
#include "ui/widgets/buttons.h"
|
|
#include "ui/widgets/shadow.h"
|
|
#include "ui/ui_utility.h"
|
|
#include "ui/widgets/rp_window.h"
|
|
#include "styles/style_widgets.h"
|
|
#include "styles/palette.h"
|
|
#include "base/algorithm.h"
|
|
#include "base/event_filter.h"
|
|
#include "ui/integration.h"
|
|
#include "base/platform/base_platform_info.h"
|
|
|
|
#include <QtGui/QPainter>
|
|
#include <QtGui/QtEvents>
|
|
#include <QtGui/QWindow>
|
|
|
|
namespace Ui {
|
|
namespace Platform {
|
|
|
|
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::on_next([=](HitTestResult button) {
|
|
controls->buttonOver(button);
|
|
}, lifetime);
|
|
|
|
window->systemButtonDown(
|
|
) | rpl::filter([=](HitTestResult button) {
|
|
return !filter || filter() || (button == HitTestResult::None);
|
|
}) | rpl::on_next([=](HitTestResult button) {
|
|
controls->buttonDown(button);
|
|
}, lifetime);
|
|
}
|
|
|
|
object_ptr<AbstractButton> IconTitleButtons::create(
|
|
not_null<QWidget*> parent,
|
|
TitleControl control,
|
|
const style::WindowTitle &st) {
|
|
const auto make = [&](
|
|
QPointer<IconButton> &my,
|
|
const style::IconButton &st) {
|
|
Expects(!my);
|
|
|
|
auto result = object_ptr<IconButton>(parent, st);
|
|
my = result.data();
|
|
return result;
|
|
};
|
|
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);
|
|
_minimize->setAccessibleName(Ui::Integration::Instance().phraseMinimize());
|
|
}
|
|
if (_maximizeRestore) {
|
|
if (maximized) {
|
|
const auto restore = active
|
|
? &st.restoreIconActive
|
|
: &st.restoreIcon;
|
|
const auto restoreOver = active
|
|
? &st.restoreIconActiveOver
|
|
: &st.restoreIconOver;
|
|
_maximizeRestore->setIconOverride(restore, restoreOver);
|
|
_maximizeRestore->setAccessibleName(Ui::Integration::Instance().phraseRestore());
|
|
} else {
|
|
const auto maximize = active
|
|
? &st.maximizeIconActive
|
|
: &st.maximize.icon;
|
|
const auto maximizeOver = active
|
|
? &st.maximizeIconActiveOver
|
|
: &st.maximize.iconOver;
|
|
_maximizeRestore->setIconOverride(maximize, maximizeOver);
|
|
_maximizeRestore->setAccessibleName(Ui::Integration::Instance().phraseMaximize());
|
|
}
|
|
}
|
|
if (_close) {
|
|
const auto close = active
|
|
? &st.closeIconActive
|
|
: &st.close.icon;
|
|
const auto closeOver = active
|
|
? &st.closeIconActiveOver
|
|
: &st.close.iconOver;
|
|
_close->setIconOverride(close, closeOver);
|
|
_close->setAccessibleName(Ui::Integration::Instance().phraseButtonClose());
|
|
}
|
|
}
|
|
|
|
TitleControls::TitleControls(
|
|
not_null<RpWidget*> parent,
|
|
const style::WindowTitle &st,
|
|
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)
|
|
, _layout(TitleControlsLayout::Instance())
|
|
, _buttons(std::move(buttons))
|
|
, _minimize(_buttons->create(parent, Control::Minimize, st))
|
|
, _maximizeRestore(_buttons->create(parent, Control::Maximize, st))
|
|
, _close(_buttons->create(parent, Control::Close, st))
|
|
, _maximizedState(parent->windowState()
|
|
& (Qt::WindowMaximized | Qt::WindowFullScreen))
|
|
, _activeState(parent->isActiveWindow()) {
|
|
init(std::move(maximize));
|
|
|
|
_close->paintRequest(
|
|
) | rpl::on_next([=] {
|
|
const auto active = window()->isActiveWindow();
|
|
if (_activeState != active) {
|
|
_activeState = active;
|
|
updateButtonsState();
|
|
}
|
|
}, _close->lifetime());
|
|
}
|
|
|
|
void TitleControls::setStyle(const style::WindowTitle &st) {
|
|
_st = &st;
|
|
updateButtonsState();
|
|
}
|
|
|
|
not_null<const style::WindowTitle*> TitleControls::st() const {
|
|
return _st;
|
|
}
|
|
|
|
TitleControlsLayout &TitleControls::layout() const {
|
|
return *_layout;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
not_null<RpWidget*> TitleControls::parent() const {
|
|
return static_cast<RpWidget*>(_close->parentWidget());
|
|
}
|
|
|
|
not_null<QWidget*> TitleControls::window() const {
|
|
return _close->window();
|
|
}
|
|
|
|
void TitleControls::init(Fn<void(bool maximized)> maximize) {
|
|
_minimize->setClickedCallback([=] {
|
|
const auto weak = base::make_weak(_minimize.data());
|
|
window()->setWindowState(
|
|
window()->windowState() | Qt::WindowMinimized);
|
|
if (weak) {
|
|
_minimize->clearState();
|
|
}
|
|
});
|
|
_minimize->setPointerCursor(false);
|
|
_maximizeRestore->setClickedCallback([=] {
|
|
const auto weak = base::make_weak(_maximizeRestore.data());
|
|
if (maximize) {
|
|
maximize(!_maximizedState);
|
|
} else {
|
|
window()->setWindowState(_maximizedState
|
|
? Qt::WindowNoState
|
|
: Qt::WindowMaximized);
|
|
}
|
|
if (weak) {
|
|
_maximizeRestore->clearState();
|
|
}
|
|
});
|
|
_maximizeRestore->setPointerCursor(false);
|
|
_close->setClickedCallback([=] {
|
|
const auto weak = base::make_weak(_close.data());
|
|
window()->close();
|
|
if (weak) {
|
|
_close->clearState();
|
|
}
|
|
});
|
|
_close->setPointerCursor(false);
|
|
|
|
rpl::combine(
|
|
parent()->widthValue(),
|
|
_layout->value()
|
|
) | rpl::on_next([=] {
|
|
updateControlsPosition();
|
|
}, _close->lifetime());
|
|
|
|
base::install_event_filter(window(), [=](not_null<QEvent*> e) {
|
|
if (e->type() == QEvent::WindowStateChange) {
|
|
handleWindowStateChanged(window()->windowState());
|
|
}
|
|
return base::EventFilterResult::Continue;
|
|
});
|
|
|
|
_activeState = parent()->isActiveWindow();
|
|
updateButtonsState();
|
|
}
|
|
|
|
void TitleControls::setResizeEnabled(bool enabled) {
|
|
_resizeEnabled = enabled;
|
|
updateControlsPosition();
|
|
}
|
|
|
|
void TitleControls::raise() {
|
|
_minimize->raise();
|
|
_maximizeRestore->raise();
|
|
_close->raise();
|
|
}
|
|
|
|
HitTestResult TitleControls::hitTest(QPoint point) const {
|
|
const auto test = [&](const object_ptr<AbstractButton> &button) {
|
|
return button && QRect(
|
|
button->mapTo(button->window(), QPoint()),
|
|
button->size()
|
|
).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<AbstractButton> &button,
|
|
HitTestResult buttonTestResult,
|
|
Control control) {
|
|
const auto over = (testResult == buttonTestResult);
|
|
if (const auto raw = button.data()) {
|
|
raw->setSynteticOver(over);
|
|
}
|
|
_buttons->notifySynteticOver(control, over);
|
|
};
|
|
update(_minimize, HitTestResult::Minimize, Control::Minimize);
|
|
update(
|
|
_maximizeRestore,
|
|
HitTestResult::MaximizeRestore,
|
|
Control::Maximize);
|
|
update(_close, HitTestResult::Close, Control::Close);
|
|
}
|
|
|
|
void TitleControls::buttonDown(HitTestResult testResult) {
|
|
const auto update = [&](
|
|
const object_ptr<AbstractButton> &button,
|
|
HitTestResult buttonTestResult) {
|
|
if (const auto raw = button.data()) {
|
|
raw->setSynteticDown(testResult == buttonTestResult);
|
|
}
|
|
};
|
|
update(_minimize, HitTestResult::Minimize);
|
|
update(_maximizeRestore, HitTestResult::MaximizeRestore);
|
|
update(_close, HitTestResult::Close);
|
|
}
|
|
|
|
AbstractButton *TitleControls::controlWidget(Control control) const {
|
|
switch (control) {
|
|
case Control::Minimize: return _minimize;
|
|
case Control::Maximize: return _maximizeRestore;
|
|
case Control::Close: return _close;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void TitleControls::updateControlsPosition() {
|
|
auto controlsLayout = _layout->current();
|
|
auto &controlsLeft = controlsLayout.left;
|
|
auto &controlsRight = controlsLayout.right;
|
|
ranges::reverse(controlsRight);
|
|
|
|
if (_st->oneSideControls) {
|
|
const auto moveFromTo = [&](auto &from, auto &to) {
|
|
for (const auto control : from) {
|
|
if (!ranges::contains(to, control)) {
|
|
to.push_back(control);
|
|
}
|
|
}
|
|
from.clear();
|
|
};
|
|
if (controlsLayout.onLeft()) {
|
|
moveFromTo(controlsRight, controlsLeft);
|
|
} else {
|
|
moveFromTo(controlsLeft, controlsRight);
|
|
}
|
|
}
|
|
|
|
const auto controlPresent = [&](Control control) {
|
|
return ranges::contains(controlsLeft, control)
|
|
|| ranges::contains(controlsRight, control);
|
|
};
|
|
|
|
const auto eraseControl = [&](Control control) {
|
|
controlsLeft.erase(
|
|
ranges::remove(controlsLeft, control),
|
|
end(controlsLeft));
|
|
|
|
controlsRight.erase(
|
|
ranges::remove(controlsRight, control),
|
|
end(controlsRight));
|
|
};
|
|
|
|
if (!_resizeEnabled) {
|
|
eraseControl(Control::Maximize);
|
|
}
|
|
|
|
if (controlPresent(Control::Minimize)) {
|
|
_minimize->show();
|
|
} else {
|
|
_minimize->hide();
|
|
}
|
|
|
|
if (controlPresent(Control::Maximize)) {
|
|
_maximizeRestore->show();
|
|
} else {
|
|
_maximizeRestore->hide();
|
|
}
|
|
|
|
if (controlPresent(Control::Close)) {
|
|
_close->show();
|
|
} else {
|
|
_close->hide();
|
|
}
|
|
|
|
std::vector<Control> visitedControls;
|
|
const auto updateBySide = [&](
|
|
const std::vector<Control> &controls,
|
|
bool right) {
|
|
auto position = 0;
|
|
for (const auto &control : controls) {
|
|
const auto widget = controlWidget(control);
|
|
if (!widget || ranges::contains(visitedControls, control)) {
|
|
continue;
|
|
}
|
|
|
|
if (right) {
|
|
widget->moveToRight(position, 0);
|
|
} else {
|
|
widget->moveToLeft(position, 0);
|
|
}
|
|
|
|
position += widget->width();
|
|
visitedControls.push_back(control);
|
|
}
|
|
};
|
|
|
|
updateBySide(controlsLeft, false);
|
|
updateBySide(controlsRight, true);
|
|
}
|
|
|
|
void TitleControls::handleWindowStateChanged(Qt::WindowStates state) {
|
|
if (state & Qt::WindowMinimized) {
|
|
return;
|
|
}
|
|
|
|
auto maximized = (state & Qt::WindowMaximized)
|
|
|| (state & Qt::WindowFullScreen);
|
|
if (_maximizedState != maximized) {
|
|
_maximizedState = maximized;
|
|
updateButtonsState();
|
|
}
|
|
}
|
|
|
|
void TitleControls::updateButtonsState() {
|
|
_buttons->updateState(_activeState, _maximizedState, *_st);
|
|
}
|
|
|
|
std::shared_ptr<TitleControlsLayout> TitleControlsLayout::Instance() {
|
|
static std::weak_ptr<TitleControlsLayout> Weak;
|
|
auto result = Weak.lock();
|
|
if (!result) {
|
|
Weak = result = Create();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DefaultTitleWidget::DefaultTitleWidget(not_null<RpWidget*> parent)
|
|
: RpWidget(parent)
|
|
, _controls(this, st::defaultWindowTitle)
|
|
, _shadow(this, st::titleShadow) {
|
|
setAttribute(Qt::WA_OpaquePaintEvent);
|
|
}
|
|
|
|
not_null<const style::WindowTitle*> DefaultTitleWidget::st() const {
|
|
return _controls.st();
|
|
}
|
|
|
|
TitleControlsLayout &DefaultTitleWidget::layout() const {
|
|
return _controls.layout();
|
|
}
|
|
|
|
QRect DefaultTitleWidget::controlsGeometry() const {
|
|
return _controls.geometry();
|
|
}
|
|
|
|
void DefaultTitleWidget::setText(const QString &text) {
|
|
window()->setWindowTitle(text);
|
|
}
|
|
|
|
void DefaultTitleWidget::setStyle(const style::WindowTitle &st) {
|
|
_controls.setStyle(st);
|
|
update();
|
|
}
|
|
|
|
void DefaultTitleWidget::setResizeEnabled(bool enabled) {
|
|
_controls.setResizeEnabled(enabled);
|
|
}
|
|
|
|
void DefaultTitleWidget::paintEvent(QPaintEvent *e) {
|
|
const auto active = window()->isActiveWindow();
|
|
QPainter(this).fillRect(
|
|
e->rect(),
|
|
active ? _controls.st()->bgActive : _controls.st()->bg);
|
|
}
|
|
|
|
void DefaultTitleWidget::resizeEvent(QResizeEvent *e) {
|
|
_shadow->setGeometry(0, height() - st::lineWidth, width(), st::lineWidth);
|
|
}
|
|
|
|
void DefaultTitleWidget::mousePressEvent(QMouseEvent *e) {
|
|
if (e->button() == Qt::LeftButton) {
|
|
_mousePressed = true;
|
|
} else if (e->button() == Qt::RightButton) {
|
|
ShowWindowMenu(window(), e->windowPos().toPoint());
|
|
}
|
|
}
|
|
|
|
void DefaultTitleWidget::mouseReleaseEvent(QMouseEvent *e) {
|
|
if (e->button() == Qt::LeftButton) {
|
|
_mousePressed = false;
|
|
}
|
|
}
|
|
|
|
void DefaultTitleWidget::mouseMoveEvent(QMouseEvent *e) {
|
|
if (_mousePressed) {
|
|
window()->windowHandle()->startSystemMove();
|
|
SendSynteticMouseEvent(
|
|
this,
|
|
QEvent::MouseButtonRelease,
|
|
Qt::LeftButton);
|
|
}
|
|
}
|
|
|
|
void DefaultTitleWidget::mouseDoubleClickEvent(QMouseEvent *e) {
|
|
const auto state = window()->windowState();
|
|
if (state & Qt::WindowMaximized) {
|
|
window()->setWindowState(state & ~Qt::WindowMaximized);
|
|
} else {
|
|
window()->setWindowState(state | Qt::WindowMaximized);
|
|
}
|
|
}
|
|
|
|
SeparateTitleControls::SeparateTitleControls(
|
|
QWidget *parent,
|
|
const style::WindowTitle &st,
|
|
Fn<void(bool maximized)> maximize)
|
|
: wrap(parent)
|
|
, 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(
|
|
not_null<RpWindow*> window,
|
|
const style::WindowTitle &st,
|
|
Fn<void(bool maximized)> maximize,
|
|
rpl::producer<int> controlsTop) {
|
|
return SetupSeparateTitleControls(
|
|
window,
|
|
std::make_unique<SeparateTitleControls>(
|
|
window->body(),
|
|
st,
|
|
std::move(maximize)),
|
|
std::move(controlsTop));
|
|
}
|
|
|
|
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();
|
|
rpl::combine(
|
|
window->body()->widthValue(),
|
|
window->additionalContentPaddingValue(),
|
|
controlsTop ? std::move(controlsTop) : rpl::single(0)
|
|
) | rpl::on_next([=](int width, int padding, int top) {
|
|
raw->wrap.setGeometry(
|
|
0,
|
|
top,
|
|
width,
|
|
raw->controls.geometry().height());
|
|
}, lifetime);
|
|
|
|
window->hitTestRequests(
|
|
) | rpl::on_next([=](not_null<HitTestRequest*> request) {
|
|
const auto controlsResult = raw->controls.hitTest(request->point);
|
|
if (controlsResult != HitTestResult::None) {
|
|
request->result = controlsResult;
|
|
}
|
|
}, lifetime);
|
|
|
|
SetupSemiNativeSystemButtons(&raw->controls, window, lifetime);
|
|
|
|
return created;
|
|
}
|
|
|
|
} // namespace Platform
|
|
} // namespace Ui
|