494 lines
16 KiB
C++
494 lines
16 KiB
C++
#include "Frameless.h"
|
|
#include "Utilities.h"
|
|
#include <QQuickWindow>
|
|
#include <dwmapi.h>
|
|
#include <qt_windows.h>
|
|
#include <windowsx.h>
|
|
|
|
static inline void setShadow(HWND hwnd) {
|
|
const MARGINS shadow = {1, 0, 0, 0};
|
|
typedef HRESULT(WINAPI * DwmExtendFrameIntoClientAreaPtr)(HWND hWnd, const MARGINS *pMarInset);
|
|
HMODULE module = LoadLibrary(L"dwmapi.dll");
|
|
if (module) {
|
|
DwmExtendFrameIntoClientAreaPtr dwm_extendframe_into_client_area_;
|
|
dwm_extendframe_into_client_area_ =
|
|
reinterpret_cast<DwmExtendFrameIntoClientAreaPtr>(GetProcAddress(module, "DwmExtendFrameIntoClientArea"));
|
|
if (dwm_extendframe_into_client_area_) {
|
|
dwm_extendframe_into_client_area_(hwnd, &shadow);
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool containsCursorToItem(QQuickItem *item) {
|
|
auto window = item->window();
|
|
if ((window == nullptr) || !item || !item->isVisible()) {
|
|
return false;
|
|
}
|
|
auto point = window->mapFromGlobal(QCursor::pos());
|
|
auto rect = QRectF(item->mapToItem(window->contentItem(), QPointF(0, 0)), item->size());
|
|
if (rect.contains(point)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Frameless::Frameless(QQuickItem *parent) : QQuickItem{parent} {
|
|
m_isWindows11OrGreater = Utilities::instance()->isWindows11OrGreater();
|
|
}
|
|
|
|
QQuickItem *Frameless::appBar() const {
|
|
return m_appBar;
|
|
}
|
|
|
|
void Frameless::setAppBar(QQuickItem *appBar) {
|
|
if (m_appBar != appBar) {
|
|
m_appBar = appBar;
|
|
emit appBarChanged();
|
|
}
|
|
}
|
|
|
|
QQuickItem *Frameless::maximizeButton() const {
|
|
return m_maximizeButton;
|
|
}
|
|
|
|
void Frameless::setMaximizeButton(QQuickItem *button) {
|
|
if (m_maximizeButton != button) {
|
|
m_maximizeButton = button;
|
|
emit maximizeButtonChanged();
|
|
}
|
|
}
|
|
|
|
QQuickItem *Frameless::minimizedButton() const {
|
|
return m_minimizedButton;
|
|
}
|
|
|
|
void Frameless::setMinimizedButton(QQuickItem *button) {
|
|
if (m_minimizedButton != button) {
|
|
m_minimizedButton = button;
|
|
emit minimizedButtonChanged();
|
|
}
|
|
}
|
|
|
|
QQuickItem *Frameless::closeButton() const {
|
|
return m_closeButton;
|
|
}
|
|
|
|
void Frameless::setCloseButton(QQuickItem *button) {
|
|
if (m_closeButton != button) {
|
|
m_closeButton = button;
|
|
emit closeButtonChanged();
|
|
}
|
|
}
|
|
|
|
bool Frameless::fixSize() const {
|
|
return m_fixSize;
|
|
}
|
|
|
|
void Frameless::setFixSize(bool fix) {
|
|
if (m_fixSize != fix) {
|
|
m_fixSize = fix;
|
|
emit fixSizeChanged();
|
|
}
|
|
}
|
|
|
|
bool Frameless::topmost() const {
|
|
return m_topmost;
|
|
}
|
|
|
|
void Frameless::setTopmost(bool topmost) {
|
|
if (m_topmost != topmost) {
|
|
m_topmost = topmost;
|
|
emit topmostChanged();
|
|
}
|
|
}
|
|
|
|
bool Frameless::disabled() const {
|
|
return m_disabled;
|
|
}
|
|
|
|
void Frameless::setDisabled(bool disabled) {
|
|
if (m_disabled != disabled) {
|
|
m_disabled = disabled;
|
|
emit disabledChanged();
|
|
}
|
|
}
|
|
|
|
void Frameless::setHitTestVisible(QQuickItem *item) {
|
|
if (!m_hitTestList.contains(item)) {
|
|
m_hitTestList.append(item);
|
|
}
|
|
}
|
|
|
|
void Frameless::onDestruction() {
|
|
QGuiApplication::instance()->removeNativeEventFilter(this);
|
|
}
|
|
|
|
void Frameless::componentComplete() {
|
|
if (m_disabled) return;
|
|
int w = window()->width();
|
|
int h = window()->height();
|
|
m_current = window()->winId();
|
|
window()->setFlags((window()->flags()) | Qt::CustomizeWindowHint | Qt::WindowMinimizeButtonHint |
|
|
Qt::WindowCloseButtonHint);
|
|
if (!m_fixSize) {
|
|
window()->setFlag(Qt::WindowMaximizeButtonHint);
|
|
}
|
|
window()->installEventFilter(this);
|
|
QGuiApplication::instance()->installNativeEventFilter(this);
|
|
if (m_maximizeButton) {
|
|
setHitTestVisible(m_maximizeButton);
|
|
}
|
|
if (m_minimizedButton) {
|
|
setHitTestVisible(m_minimizedButton);
|
|
}
|
|
if (m_closeButton) {
|
|
setHitTestVisible(m_closeButton);
|
|
}
|
|
#ifdef Q_OS_WIN
|
|
#if (QT_VERSION == QT_VERSION_CHECK(6, 5, 3))
|
|
qWarning() << "Qt's own frameless bug, currently only exist in 6.5.3, please use other versions";
|
|
#endif
|
|
HWND hwnd = reinterpret_cast<HWND>(window()->winId());
|
|
DWORD style = ::GetWindowLongPtr(hwnd, GWL_STYLE);
|
|
if (m_fixSize) {
|
|
::SetWindowLongPtr(hwnd, GWL_STYLE, style | WS_THICKFRAME | WS_CAPTION);
|
|
for (int i = 0; i <= QGuiApplication::screens().count() - 1; ++i) {
|
|
connect(QGuiApplication::screens().at(i), &QScreen::logicalDotsPerInchChanged, this, [=] {
|
|
SetWindowPos(hwnd, nullptr, 0, 0, 0, 0,
|
|
SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOMOVE | SWP_FRAMECHANGED);
|
|
});
|
|
}
|
|
} else {
|
|
::SetWindowLongPtr(hwnd, GWL_STYLE, style | WS_MAXIMIZEBOX | WS_THICKFRAME | WS_CAPTION);
|
|
}
|
|
SetWindowPos(hwnd, nullptr, 0, 0, 0, 0,
|
|
SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED);
|
|
connect(window(), &QQuickWindow::screenChanged, this, [hwnd] {
|
|
::SetWindowPos(hwnd, nullptr, 0, 0, 0, 0,
|
|
SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED | SWP_NOOWNERZORDER);
|
|
::RedrawWindow(hwnd, nullptr, nullptr, RDW_INVALIDATE | RDW_UPDATENOW);
|
|
});
|
|
if (!window()->property("_hideShadow").toBool()) {
|
|
setShadow(hwnd);
|
|
}
|
|
#endif
|
|
auto appBarHeight = m_appBar->height();
|
|
h = qRound(h + appBarHeight);
|
|
if (m_fixSize) {
|
|
window()->setMaximumSize(QSize(w, h));
|
|
window()->setMinimumSize(QSize(w, h));
|
|
} else {
|
|
window()->setMinimumHeight(window()->minimumHeight() + appBarHeight);
|
|
window()->setMaximumHeight(window()->maximumHeight() + appBarHeight);
|
|
}
|
|
window()->resize(QSize(w, h));
|
|
connect(this, &Frameless::topmostChanged, this, [this] { setWindowTopmost(topmost()); });
|
|
setWindowTopmost(topmost());
|
|
}
|
|
|
|
bool Frameless::eventFilter(QObject *obj, QEvent *event) {
|
|
#ifndef Q_OS_WIN
|
|
switch (ev->type()) {
|
|
case QEvent::MouseButtonPress:
|
|
if (_edges != 0) {
|
|
QMouseEvent *event = static_cast<QMouseEvent *>(ev);
|
|
if (event->button() == Qt::LeftButton) {
|
|
_updateCursor(_edges);
|
|
window()->startSystemResize(Qt::Edges(_edges));
|
|
}
|
|
} else {
|
|
if (_hitAppBar()) {
|
|
qint64 clickTimer = QDateTime::currentMSecsSinceEpoch();
|
|
qint64 offset = clickTimer - this->_clickTimer;
|
|
this->_clickTimer = clickTimer;
|
|
if (offset < 300) {
|
|
if (_isMaximized()) {
|
|
showNormal();
|
|
} else {
|
|
showMaximized();
|
|
}
|
|
} else {
|
|
window()->startSystemMove();
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case QEvent::MouseButtonRelease:
|
|
_edges = 0;
|
|
break;
|
|
case QEvent::MouseMove: {
|
|
if (_isMaximized() || _isFullScreen()) {
|
|
break;
|
|
}
|
|
if (_fixSize) {
|
|
break;
|
|
}
|
|
QMouseEvent *event = static_cast<QMouseEvent *>(ev);
|
|
QPoint p =
|
|
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
|
event->pos();
|
|
#else
|
|
event->position().toPoint();
|
|
#endif
|
|
if (p.x() >= _margins && p.x() <= (window()->width() - _margins) && p.y() >= _margins &&
|
|
p.y() <= (window()->height() - _margins)) {
|
|
if (_edges != 0) {
|
|
_edges = 0;
|
|
_updateCursor(_edges);
|
|
}
|
|
break;
|
|
}
|
|
_edges = 0;
|
|
if (p.x() < _margins) {
|
|
_edges |= Qt::LeftEdge;
|
|
}
|
|
if (p.x() > (window()->width() - _margins)) {
|
|
_edges |= Qt::RightEdge;
|
|
}
|
|
if (p.y() < _margins) {
|
|
_edges |= Qt::TopEdge;
|
|
}
|
|
if (p.y() > (window()->height() - _margins)) {
|
|
_edges |= Qt::BottomEdge;
|
|
}
|
|
_updateCursor(_edges);
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
#endif
|
|
return QObject::eventFilter(obj, event);
|
|
}
|
|
|
|
bool Frameless::nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result) {
|
|
#ifdef Q_OS_WIN
|
|
if ((eventType != "windows_generic_MSG") || !message) {
|
|
return false;
|
|
}
|
|
const auto msg = static_cast<const MSG *>(message);
|
|
auto hwnd = msg->hwnd;
|
|
if (!hwnd) {
|
|
return false;
|
|
}
|
|
const quint64 wid = reinterpret_cast<qint64>(hwnd);
|
|
if (wid != m_current) {
|
|
return false;
|
|
}
|
|
const auto uMsg = msg->message;
|
|
const auto wParam = msg->wParam;
|
|
const auto lParam = msg->lParam;
|
|
if (uMsg == WM_WINDOWPOSCHANGING) {
|
|
auto *wp = reinterpret_cast<WINDOWPOS *>(lParam);
|
|
if (wp != nullptr && (wp->flags & SWP_NOSIZE) == 0) {
|
|
wp->flags |= SWP_NOCOPYBITS;
|
|
*result = static_cast<qintptr>(::DefWindowProcW(hwnd, uMsg, wParam, lParam));
|
|
return true;
|
|
}
|
|
return false;
|
|
} else if (uMsg == WM_NCCALCSIZE && wParam == TRUE) {
|
|
bool isMaximum = ::IsZoomed(hwnd);
|
|
if (isMaximum) {
|
|
window()->setProperty("__margins", 7);
|
|
} else {
|
|
window()->setProperty("__margins", 0);
|
|
}
|
|
setMaximizeHovered(false);
|
|
*result = WVR_REDRAW;
|
|
return true;
|
|
} else if (uMsg == WM_NCHITTEST) {
|
|
if (m_isWindows11OrGreater) {
|
|
if (hitMaximizeButton()) {
|
|
if (*result == HTNOWHERE) {
|
|
*result = HTZOOM;
|
|
}
|
|
setMaximizeHovered(true);
|
|
return true;
|
|
}
|
|
setMaximizeHovered(false);
|
|
setMaximizePressed(false);
|
|
}
|
|
*result = 0;
|
|
POINT nativeGlobalPos{GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
|
|
POINT nativeLocalPos = nativeGlobalPos;
|
|
::ScreenToClient(hwnd, &nativeLocalPos);
|
|
RECT clientRect{0, 0, 0, 0};
|
|
::GetClientRect(hwnd, &clientRect);
|
|
auto clientWidth = clientRect.right - clientRect.left;
|
|
auto clientHeight = clientRect.bottom - clientRect.top;
|
|
bool left = nativeLocalPos.x < m_margins;
|
|
bool right = nativeLocalPos.x > clientWidth - m_margins;
|
|
bool top = nativeLocalPos.y < m_margins;
|
|
bool bottom = nativeLocalPos.y > clientHeight - m_margins;
|
|
*result = 0;
|
|
if (!m_fixSize && !isFullScreen() && !isMaximized()) {
|
|
if (left && top) {
|
|
*result = HTTOPLEFT;
|
|
} else if (left && bottom) {
|
|
*result = HTBOTTOMLEFT;
|
|
} else if (right && top) {
|
|
*result = HTTOPRIGHT;
|
|
} else if (right && bottom) {
|
|
*result = HTBOTTOMRIGHT;
|
|
} else if (left) {
|
|
*result = HTLEFT;
|
|
} else if (right) {
|
|
*result = HTRIGHT;
|
|
} else if (top) {
|
|
*result = HTTOP;
|
|
} else if (bottom) {
|
|
*result = HTBOTTOM;
|
|
}
|
|
}
|
|
if (0 != *result) {
|
|
return true;
|
|
}
|
|
if (hitAppBar()) {
|
|
*result = HTCAPTION;
|
|
return true;
|
|
}
|
|
*result = HTCLIENT;
|
|
return true;
|
|
} else if (uMsg == WM_NCPAINT) {
|
|
*result = FALSE;
|
|
return false;
|
|
} else if (uMsg == WM_NCACTIVATE) {
|
|
*result = TRUE;
|
|
return true;
|
|
} else if (m_isWindows11OrGreater && (uMsg == WM_NCLBUTTONDBLCLK || uMsg == WM_NCLBUTTONDOWN)) {
|
|
if (hitMaximizeButton()) {
|
|
QMouseEvent event = QMouseEvent(QEvent::MouseButtonPress, QPoint(), QPoint(), Qt::LeftButton,
|
|
Qt::LeftButton, Qt::NoModifier);
|
|
QGuiApplication::sendEvent(m_maximizeButton, &event);
|
|
setMaximizePressed(true);
|
|
return true;
|
|
}
|
|
} else if (m_isWindows11OrGreater && (uMsg == WM_NCLBUTTONUP || uMsg == WM_NCRBUTTONUP)) {
|
|
if (hitMaximizeButton()) {
|
|
QMouseEvent event = QMouseEvent(QEvent::MouseButtonRelease, QPoint(), QPoint(), Qt::LeftButton,
|
|
Qt::LeftButton, Qt::NoModifier);
|
|
QGuiApplication::sendEvent(m_maximizeButton, &event);
|
|
setMaximizePressed(false);
|
|
return true;
|
|
}
|
|
} else if (uMsg == WM_NCRBUTTONDOWN) {
|
|
if (wParam == HTCAPTION) {
|
|
auto pos = window()->position();
|
|
auto offset = window()->mapFromGlobal(QCursor::pos());
|
|
showSystemMenu(QPoint(pos.x() + offset.x(), pos.y() + offset.y()));
|
|
}
|
|
} else if (uMsg == WM_KEYDOWN || uMsg == WM_SYSKEYDOWN) {
|
|
const bool altPressed = ((wParam == VK_MENU) || (::GetKeyState(VK_MENU) < 0));
|
|
const bool spacePressed = ((wParam == VK_SPACE) || (::GetKeyState(VK_SPACE) < 0));
|
|
if (altPressed && spacePressed) {
|
|
auto pos = window()->position();
|
|
showSystemMenu(QPoint(pos.x(), qRound(pos.y() + m_appBar->height())));
|
|
}
|
|
} else if (uMsg == WM_SYSCOMMAND) {
|
|
if (wParam == SC_MINIMIZE) {
|
|
if (window()->transientParent()) {
|
|
auto _hwnd = reinterpret_cast<HWND>(window()->transientParent()->winId());
|
|
::ShowWindow(_hwnd, 2);
|
|
} else {
|
|
auto _hwnd = reinterpret_cast<HWND>(window()->winId());
|
|
::ShowWindow(_hwnd, 2);
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
return false;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
bool Frameless::isFullScreen() {
|
|
return window()->visibility() == QWindow::FullScreen;
|
|
}
|
|
|
|
bool Frameless::isMaximized() {
|
|
return window()->visibility() == QWindow::Maximized;
|
|
}
|
|
|
|
void Frameless::setMaximizeHovered(bool val) {
|
|
if (m_maximizeButton) {
|
|
m_maximizeButton->setProperty("hover", val);
|
|
}
|
|
}
|
|
|
|
void Frameless::setMaximizePressed(bool val) {
|
|
if (m_maximizeButton) {
|
|
m_maximizeButton->setProperty("down", val);
|
|
}
|
|
}
|
|
|
|
void Frameless::setWindowTopmost(bool topmost) {
|
|
#ifdef Q_OS_WIN
|
|
HWND hwnd = reinterpret_cast<HWND>(window()->winId());
|
|
if (topmost) {
|
|
::SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
|
|
} else {
|
|
::SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
|
|
}
|
|
#else
|
|
window()->setFlag(Qt::WindowStaysOnTopHint, topmost);
|
|
#endif
|
|
}
|
|
|
|
bool Frameless::hitMaximizeButton() {
|
|
if (containsCursorToItem(m_maximizeButton)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Frameless::hitAppBar() {
|
|
for (int i = 0; i <= m_hitTestList.size() - 1; ++i) {
|
|
auto item = m_hitTestList.at(i);
|
|
if (containsCursorToItem(item)) {
|
|
return false;
|
|
}
|
|
}
|
|
if ((m_appBar != nullptr) && containsCursorToItem(m_appBar)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Frameless::showSystemMenu(QPoint point) {
|
|
#ifdef Q_OS_WIN
|
|
QScreen *screen = window()->screen();
|
|
if (!screen) {
|
|
screen = QGuiApplication::primaryScreen();
|
|
}
|
|
if (!screen) {
|
|
return;
|
|
}
|
|
const QPoint origin = screen->geometry().topLeft();
|
|
auto nativePos = QPointF(QPointF(point - origin) * window()->devicePixelRatio()).toPoint() + origin;
|
|
HWND hwnd = reinterpret_cast<HWND>(window()->winId());
|
|
auto hMenu = ::GetSystemMenu(hwnd, FALSE);
|
|
if (isMaximized() || isFullScreen()) {
|
|
::EnableMenuItem(hMenu, SC_MOVE, MFS_DISABLED);
|
|
::EnableMenuItem(hMenu, SC_RESTORE, MFS_ENABLED);
|
|
} else {
|
|
::EnableMenuItem(hMenu, SC_MOVE, MFS_ENABLED);
|
|
::EnableMenuItem(hMenu, SC_RESTORE, MFS_DISABLED);
|
|
}
|
|
if (!m_fixSize && !isMaximized() && !isFullScreen()) {
|
|
::EnableMenuItem(hMenu, SC_SIZE, MFS_ENABLED);
|
|
::EnableMenuItem(hMenu, SC_MAXIMIZE, MFS_ENABLED);
|
|
} else {
|
|
::EnableMenuItem(hMenu, SC_SIZE, MFS_DISABLED);
|
|
::EnableMenuItem(hMenu, SC_MAXIMIZE, MFS_DISABLED);
|
|
}
|
|
const int result =
|
|
::TrackPopupMenu(hMenu, (TPM_RETURNCMD | (QGuiApplication::isRightToLeft() ? TPM_RIGHTALIGN : TPM_LEFTALIGN)),
|
|
nativePos.x(), nativePos.y(), 0, hwnd, nullptr);
|
|
if (result) {
|
|
::PostMessageW(hwnd, WM_SYSCOMMAND, result, 0);
|
|
}
|
|
#endif
|
|
}
|