framelesshelper/framelesshelper.cpp

320 lines
12 KiB
C++

/*
* MIT License
*
* Copyright (C) 2021 by wangwenx190 (Yuhang Zhao)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "framelesshelper.h"
#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
#include <QtCore/qdebug.h>
#include <QtCore/qcoreevent.h>
#include <QtGui/qevent.h>
#include <QtGui/qwindow.h>
FramelessHelper::FramelessHelper(QObject *parent) : QObject(parent) {}
int FramelessHelper::getBorderWidth() const
{
return m_borderWidth;
}
void FramelessHelper::setBorderWidth(const int val)
{
m_borderWidth = val;
}
int FramelessHelper::getBorderHeight() const
{
return m_borderHeight;
}
void FramelessHelper::setBorderHeight(const int val)
{
m_borderHeight = val;
}
int FramelessHelper::getTitleBarHeight() const
{
return m_titleBarHeight;
}
void FramelessHelper::setTitleBarHeight(const int val)
{
m_titleBarHeight = val;
}
QObjectList FramelessHelper::getIgnoreObjects(const QWindow *window) const
{
Q_ASSERT(window);
QObjectList ret{};
const QObjectList objs = m_ignoreObjects.value(window);
if (!objs.isEmpty()) {
for (auto &&_obj : qAsConst(objs)) {
if (_obj) {
ret.append(_obj);
}
}
}
return ret;
}
void FramelessHelper::addIgnoreObject(const QWindow *window, QObject *val)
{
Q_ASSERT(window);
QObjectList objs = m_ignoreObjects[window];
objs.append(val);
m_ignoreObjects[window] = objs;
}
bool FramelessHelper::getResizable(const QWindow *window) const
{
Q_ASSERT(window);
return !m_fixedSize.value(window);
}
void FramelessHelper::setResizable(const QWindow *window, const bool val)
{
Q_ASSERT(window);
m_fixedSize[window] = !val;
}
void FramelessHelper::removeWindowFrame(QWindow *window)
{
Q_ASSERT(window);
// TODO: check whether these flags are correct for Linux and macOS.
window->setFlags(Qt::Window | Qt::FramelessWindowHint | Qt::WindowSystemMenuHint
| Qt::WindowMinMaxButtonsHint | Qt::WindowTitleHint);
// MouseTracking is always enabled for QWindow.
window->installEventFilter(this);
}
bool FramelessHelper::eventFilter(QObject *object, QEvent *event)
{
Q_ASSERT(object);
Q_ASSERT(event);
if (!object->isWindowType()) {
return false;
}
// QWindow will always be a top level window. It can't
// be anyone's child window.
const auto currentWindow = qobject_cast<QWindow *>(object);
static bool m_bIsMRBPressed = false;
static QPointF m_pOldMousePos = {};
const auto getWindowEdges =
[this](const QPointF &point, const int ww, const int wh) -> Qt::Edges {
if (point.y() <= m_borderHeight) {
if (point.x() <= m_borderWidth) {
return Qt::Edge::TopEdge | Qt::Edge::LeftEdge;
}
if (point.x() >= (ww - m_borderWidth)) {
return Qt::Edge::TopEdge | Qt::Edge::RightEdge;
}
return Qt::Edge::TopEdge;
}
if (point.y() >= (wh - m_borderHeight)) {
if (point.x() <= m_borderWidth) {
return Qt::Edge::BottomEdge | Qt::Edge::LeftEdge;
}
if (point.x() >= (ww - m_borderWidth)) {
return Qt::Edge::BottomEdge | Qt::Edge::RightEdge;
}
return Qt::Edge::BottomEdge;
}
if (point.x() <= m_borderWidth) {
return Qt::Edge::LeftEdge;
}
if (point.x() >= (ww - m_borderWidth)) {
return Qt::Edge::RightEdge;
}
return {};
};
const auto getCursorShape = [](const Qt::Edges edges) -> Qt::CursorShape {
if ((edges.testFlag(Qt::Edge::TopEdge) && edges.testFlag(Qt::Edge::LeftEdge))
|| (edges.testFlag(Qt::Edge::BottomEdge) && edges.testFlag(Qt::Edge::RightEdge))) {
return Qt::CursorShape::SizeFDiagCursor;
}
if ((edges.testFlag(Qt::Edge::TopEdge) && edges.testFlag(Qt::Edge::RightEdge))
|| (edges.testFlag(Qt::Edge::BottomEdge) && edges.testFlag(Qt::Edge::LeftEdge))) {
return Qt::CursorShape::SizeBDiagCursor;
}
if (edges.testFlag(Qt::Edge::TopEdge) || edges.testFlag(Qt::Edge::BottomEdge)) {
return Qt::CursorShape::SizeVerCursor;
}
if (edges.testFlag(Qt::Edge::LeftEdge) || edges.testFlag(Qt::Edge::RightEdge)) {
return Qt::CursorShape::SizeHorCursor;
}
return Qt::CursorShape::ArrowCursor;
};
const auto isInSpecificObjects = [](const QPointF &mousePos,
const QObjectList &objects) -> bool {
if (objects.isEmpty()) {
return false;
}
for (auto &&obj : qAsConst(objects)) {
if (!obj) {
continue;
}
if (!obj->isWidgetType() && !obj->inherits("QQuickItem")) {
qWarning() << obj << "is not a QWidget or QQuickItem!";
continue;
}
if (!obj->property("visible").toBool()) {
qDebug() << "Skipping invisible object" << obj;
continue;
}
const auto mapOriginPointToWindow = [](const QObject *obj) -> QPointF {
Q_ASSERT(obj);
if (!obj) {
return {};
}
QPointF point = {obj->property("x").toReal(), obj->property("y").toReal()};
for (QObject *parent = obj->parent(); parent; parent = parent->parent()) {
point += {parent->property("x").toReal(), parent->property("y").toReal()};
if (parent->isWindowType()) {
break;
}
}
return point;
};
const QPointF originPoint = mapOriginPointToWindow(obj);
const qreal width = obj->property("width").toReal();
const qreal height = obj->property("height").toReal();
if (QRectF(originPoint.x(), originPoint.y(), width, height).contains(mousePos)) {
return true;
}
}
return false;
};
const auto isInTitlebarArea = [this, &isInSpecificObjects](const QPointF &globalPoint,
const QPointF &point,
const QWindow *window) -> bool {
Q_ASSERT(window);
return (point.y() <= m_titleBarHeight)
&& !isInSpecificObjects(globalPoint, getIgnoreObjects(window));
};
const auto moveOrResize =
[this, &getWindowEdges, &isInTitlebarArea, &isInSpecificObjects](const QPointF &globalPoint,
const QPointF &point,
QWindow *window) {
Q_ASSERT(window);
//const QPointF deltaPoint = globalPoint - m_pOldMousePos;
const Qt::Edges edges = getWindowEdges(point, window->width(), window->height());
if (edges == Qt::Edges{}) {
if (isInTitlebarArea(globalPoint, point, window)) {
if (!window->startSystemMove()) {
// ### FIXME: TO BE IMPLEMENTED!
qWarning() << "Current OS doesn't support QWindow::startSystemMove().";
}
}
} else {
if (window->windowStates().testFlag(Qt::WindowState::WindowNoState)
&& !isInSpecificObjects(globalPoint, getIgnoreObjects(window))
&& getResizable(window)) {
if (!window->startSystemResize(edges)) {
// ### FIXME: TO BE IMPLEMENTED!
qWarning() << "Current OS doesn't support QWindow::startSystemResize().";
}
}
}
};
const auto getMousePos = [](const QMouseEvent *e, const bool global) -> QPointF {
Q_ASSERT(e);
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
return global ? e->globalPosition() : e->scenePosition();
#else
return global ? e->screenPos() : e->windowPos();
#endif
};
switch (event->type()) {
case QEvent::MouseButtonDblClick: {
const auto mouseEvent = static_cast<QMouseEvent *>(event);
if (mouseEvent) {
if (mouseEvent->button() != Qt::MouseButton::LeftButton) {
break;
}
if (isInTitlebarArea(getMousePos(mouseEvent, true),
getMousePos(mouseEvent, false),
currentWindow)) {
if (currentWindow->windowStates().testFlag(Qt::WindowState::WindowFullScreen)) {
break;
}
if (currentWindow->windowStates().testFlag(Qt::WindowState::WindowMaximized)) {
currentWindow->showNormal();
} else {
currentWindow->showMaximized();
}
currentWindow->setCursor(Qt::CursorShape::ArrowCursor);
}
}
} break;
case QEvent::MouseButtonPress: {
const auto mouseEvent = static_cast<QMouseEvent *>(event);
if (mouseEvent) {
if (mouseEvent->button() != Qt::MouseButton::LeftButton) {
break;
}
m_bIsMRBPressed = true;
m_pOldMousePos = getMousePos(mouseEvent, true);
moveOrResize(getMousePos(mouseEvent, true),
getMousePos(mouseEvent, false),
currentWindow);
}
} break;
case QEvent::MouseMove: {
const auto mouseEvent = static_cast<QMouseEvent *>(event);
if (mouseEvent) {
if (currentWindow->windowStates().testFlag(Qt::WindowState::WindowNoState)
&& getResizable(currentWindow)) {
currentWindow->setCursor(
getCursorShape(getWindowEdges(getMousePos(mouseEvent, false),
currentWindow->width(),
currentWindow->height())));
}
}
} break;
case QEvent::MouseButtonRelease: {
const auto mouseEvent = static_cast<QMouseEvent *>(event);
if (mouseEvent) {
if (mouseEvent->button() != Qt::MouseButton::LeftButton) {
break;
}
m_bIsMRBPressed = false;
m_pOldMousePos = {};
}
} break;
case QEvent::TouchBegin:
case QEvent::TouchUpdate: {
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
const auto point = static_cast<QTouchEvent *>(event)->points().first();
moveOrResize(point.globalPosition(), point.position(), currentWindow);
#else
const auto point = static_cast<QTouchEvent *>(event)->touchPoints().first();
moveOrResize(point.screenPos(), point.pos(), currentWindow);
#endif
} break;
default:
break;
}
return false;
}
#endif