lib_ui/ui/platform/win/ui_window_title_win.cpp
2023-02-10 16:42:55 +04:00

250 lines
6.5 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/win/ui_window_title_win.h"
#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"
#include "base/debug_log.h"
#include "styles/style_widgets.h"
#include "styles/palette.h"
#include <QtGui/QPainter>
#include <QtGui/QtEvents>
#include <QtGui/QWindow>
#include <windows.h>
#include <shellscalingapi.h>
namespace Ui {
namespace Platform {
namespace {
HRESULT(__stdcall *GetScaleFactorForMonitor)(
_In_ HMONITOR hMon,
_Out_ DEVICE_SCALE_FACTOR *pScale);
[[nodiscard]] bool ScaleQuerySupported() {
static const auto Result = [&] {
#define LOAD_SYMBOL(lib, name) base::Platform::LoadMethod(lib, #name, name)
const auto shcore = base::Platform::SafeLoadLibrary(L"Shcore.dll");
return LOAD_SYMBOL(shcore, GetScaleFactorForMonitor);
#undef LOAD_SYMBOL
}();
return Result;
}
} // namespace
struct TitleWidget::PaddingHelper {
explicit PaddingHelper(QWidget *parent) : controlsParent(parent) {
}
RpWidget controlsParent;
rpl::variable<int> padding = 0;
};
TitleWidget::TitleWidget(not_null<RpWidget*> parent)
: RpWidget(parent)
, _paddingHelper(CheckTitlePaddingRequired()
? std::make_unique<PaddingHelper>(this)
: nullptr)
, _controls(
_paddingHelper ? &_paddingHelper->controlsParent : this,
st::defaultWindowTitle)
, _shadow(this, st::titleShadow) {
setAttribute(Qt::WA_OpaquePaintEvent);
parent->widthValue(
) | rpl::start_with_next([=](int width) {
refreshGeometryWithWidth(width);
}, 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) {
window()->setWindowTitle(text);
}
void TitleWidget::setStyle(const style::WindowTitle &st) {
_controls.setStyle(st);
refreshGeometryWithWidth(window()->width());
}
void TitleWidget::refreshGeometryWithWidth(int width) {
const auto add = additionalPadding();
setGeometry(0, 0, width, _controls.st()->height + add);
if (_paddingHelper) {
_paddingHelper->controlsParent.setGeometry(
add,
add,
width - 2 * add,
_controls.st()->height);
}
update();
}
not_null<const style::WindowTitle*> TitleWidget::st() const {
return _controls.st();
}
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) {
const auto thickness = st::lineWidth;
_shadow->setGeometry(0, height() - thickness, width(), thickness);
}
HitTestResult TitleWidget::hitTest(QPoint point) const {
const auto origin = _paddingHelper
? _paddingHelper->controlsParent.pos()
: QPoint();
const auto padding = _paddingHelper
? _paddingHelper->padding.current()
: 0;
const auto controlsResult = _controls.hitTest(point - origin, padding);
return (controlsResult != HitTestResult::None)
? controlsResult
: HitTestResult::Caption;
}
bool TitleWidget::additionalPaddingRequired() const {
return _paddingHelper && !isHidden();
}
void TitleWidget::refreshAdditionalPaddings() {
if (!additionalPaddingRequired()) {
return;
}
const auto handle = GetWindowHandle(this);
if (!handle) {
LOG(("System Error: GetWindowHandle failed."));
return;
}
refreshAdditionalPaddings(handle);
}
void TitleWidget::refreshAdditionalPaddings(HWND handle) {
if (!additionalPaddingRequired()) {
return;
}
auto placement = WINDOWPLACEMENT{
.length = sizeof(WINDOWPLACEMENT),
};
if (!GetWindowPlacement(handle, &placement)) {
LOG(("System Error: GetWindowPlacement failed."));
return;
}
refreshAdditionalPaddings(handle, placement);
}
void TitleWidget::refreshAdditionalPaddings(
HWND handle,
const WINDOWPLACEMENT &placement) {
if (!additionalPaddingRequired()) {
return;
}
auto geometry = RECT();
if (!GetWindowRect(handle, &geometry)) {
LOG(("System Error: GetWindowRect failed."));
return;
}
const auto normal = placement.rcNormalPosition;
const auto rounded = (normal.left == geometry.left)
&& (normal.right == geometry.right)
&& (normal.top == geometry.top)
&& (normal.bottom == geometry.bottom);
const auto padding = [&] {
if (!rounded) {
return 0;
}
const auto monitor = MonitorFromWindow(
handle,
MONITOR_DEFAULTTONEAREST);
if (!monitor) {
LOG(("System Error: MonitorFromWindow failed."));
return -1;
}
auto factor = DEVICE_SCALE_FACTOR();
if (!SUCCEEDED(GetScaleFactorForMonitor(monitor, &factor))) {
LOG(("System Error: GetScaleFactorForMonitor failed."));
return -1;
} else if (factor < 100 || factor > 500) {
LOG(("System Error: Bad scale factor %1.").arg(int(factor)));
return -1;
}
const auto pixels = (factor + 50) / 100;
return int(base::SafeRound(
pixels / window()->windowHandle()->devicePixelRatio()));
}();
if (padding < 0) {
return;
}
setAdditionalPadding(padding);
}
int TitleWidget::additionalPadding() const {
return _paddingHelper ? _paddingHelper->padding.current() : 0;
}
rpl::producer<int> TitleWidget::additionalPaddingValue() const {
return _paddingHelper ? _paddingHelper->padding.value() : rpl::single(0);
}
void TitleWidget::setAdditionalPadding(int padding) {
Expects(_paddingHelper != nullptr);
padding /= window()->devicePixelRatio();
if (_paddingHelper->padding.current() == padding) {
return;
}
_paddingHelper->padding = padding;
refreshGeometryWithWidth(window()->width());
}
void TitleWidget::setVisibleHook(bool visible) {
RpWidget::setVisibleHook(visible);
if (additionalPaddingRequired()) {
PostponeCall(this, [=] {
refreshAdditionalPaddings();
});
}
}
bool CheckTitlePaddingRequired() {
return ::Platform::IsWindows11OrGreater() && ScaleQuerySupported();
}
} // namespace Platform
} // namespace Ui