qt-scene-editor/FinalProject/framelesswindow.cpp
Linloir 795b542dda
Adapt to previous changes
- Tested version, runnable
2022-12-17 11:43:22 +08:00

374 lines
17 KiB
C++

#include <qapplication.h>
#include <qpainterpath.h>
#include <qscreen.h>
#include <qlabel.h>
#include "framelesswindow.h"
#include "logger.h"
#define MAX_MOUSE_MOVEMENT 300
FramelessWindow::FramelessWindow(int cornerRadius, unsigned int attributes, QWidget* parent)
: _cornerRadius(cornerRadius), _attributes((LUI_WINDOW_ATTRIBUTES)attributes), QWidget(parent)
{
setAttribute(Qt::WA_TranslucentBackground);
setWindowFlags(Qt::FramelessWindowHint);
setFocusPolicy(Qt::StrongFocus);
setMouseTracking(true);
setFocus();
// Create and properly set real displayed window widget
_stretchLayout = new QVBoxLayout(this);
_stretchLayout->setContentsMargins(30, 30, 30, 30);
_windowWidget = new QWidget(this);
_windowWidget->setObjectName("windowWidget");
_windowWidget->setMouseTracking(true);
_stretchLayout->addWidget(_windowWidget);
_windowWidget->show();
setLayout(_stretchLayout);
// Set style sheet for window widget
QString windowWidgetStyleSheet = "QWidget#windowWidget{background-color:" + _backgroundColor.name(QColor::HexArgb) + ";border-radius:" + QString::number(_cornerRadius) + "px;}";
_windowWidget->setStyleSheet(windowWidgetStyleSheet);
// Set shadow for window widget
_windowShadow = new QGraphicsDropShadowEffect(_windowWidget);
_windowShadow->setBlurRadius(30);
_windowShadow->setColor(QColor(0, 0, 0));
_windowShadow->setOffset(0, 0);
_windowWidget->setGraphicsEffect(_windowShadow);
// Create window control buttons container widget & its layout
_windowBtnWidget = new QWidget(_windowWidget);
_windowBtnWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
_windowBtnWidget->setMouseTracking(true);
_windowBtnLayout = new QHBoxLayout(_windowBtnWidget);
_windowBtnLayout->setContentsMargins(0, 0, 0, 0);
_windowBtnLayout->setSpacing(10);
_windowBtnLayout->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
_windowBtnWidget->setLayout(_windowBtnLayout);
// Create window control buttons
_minimizeBtn = new QPushButton(_windowBtnWidget);
_maximizeBtn = new QPushButton(_windowBtnWidget);
_closeBtn = new QPushButton(_windowBtnWidget);
_minimizeBtn->setFixedSize(12, 12);
_maximizeBtn->setFixedSize(12, 12);
_closeBtn->setFixedSize(12, 12);
_minimizeBtn->setStyleSheet("QPushButton{border-radius: 6px; background-color: #c2c2c2;}"
"QPushButton:hover{background-color: #e98b2a;}");
_maximizeBtn->setStyleSheet("QPushButton{border-radius: 6px; background-color: #c2c2c2;}"
"QPushButton:hover{background-color: #2d6d4b;}");
_closeBtn->setStyleSheet("QPushButton{border-radius: 6px; background-color: #c2c2c2;}"
"QPushButton:hover{background-color: #ab3b3a;}");
_windowBtnLayout->addWidget(_minimizeBtn);
_windowBtnLayout->addWidget(_maximizeBtn);
_windowBtnLayout->addWidget(_closeBtn);
if ((_attributes & LUI_WINDOW_ATTRIBUTES::LUI_WINDOW_DISABLE_MINIMIZE) == 0) {
_minimizeBtn->show();
}
if ((_attributes & LUI_WINDOW_ATTRIBUTES::LUI_WINDOW_DISABLE_MAXIMIZE) == 0) {
_maximizeBtn->show();
}
if ((_attributes & LUI_WINDOW_ATTRIBUTES::LUI_WINDOW_DISABLE_CLOSE) == 0) {
_closeBtn->show();
}
// Connect window control buttons
connect(_minimizeBtn, &QPushButton::clicked, this, &QWidget::showMinimized);
connect(_maximizeBtn, &QPushButton::clicked, this, &FramelessWindow::controlWindowScale);
connect(_closeBtn, &QPushButton::clicked, this, &QWidget::close);
}
FramelessWindow::FramelessWindow(QWidget* parent)
: FramelessWindow(0, LUI_WINDOW_ATTRIBUTES::LUI_WINDOW_NO_ATTRIBUTES, parent)
{
}
FramelessWindow::FramelessWindow(int cornerRadius, QWidget* parent)
: FramelessWindow(cornerRadius, LUI_WINDOW_ATTRIBUTES::LUI_WINDOW_NO_ATTRIBUTES, parent)
{
}
FramelessWindow::FramelessWindow(unsigned int attributes, QWidget* parent)
: FramelessWindow(0, attributes, parent)
{
}
FramelessWindow::~FramelessWindow() {}
void FramelessWindow::showEvent(QShowEvent* event) {
// Initialize window UI after window is shown
initializeWindowUI();
}
void FramelessWindow::initializeWindowUI() {
if (_initialized) {
return;
}
// Create a round cornered mask for window widget
QPainterPath path;
path.addRoundedRect(_windowWidget->rect(), _cornerRadius - 1, _cornerRadius - 1);
QRegion region(path.toFillPolygon().toPolygon());
_windowWidget->setMask(region);
// Create a border for window widget (in order to hide zigzagged edges)
_windowBorder = new QWidget(this);
_windowBorder->setObjectName("windowBorder");
QString windowBorderStyleSheet =
"QWidget#windowBorder{background-color:#00FFFFFF;border:1.5px solid " + _borderColor.name(QColor::HexArgb) + ";border-radius:" + QString::number(_cornerRadius) + "px;}";
_windowBorder->setStyleSheet(windowBorderStyleSheet);
_windowBorder->setAttribute(Qt::WA_TransparentForMouseEvents);
_windowBorder->move(_windowWidget->pos() - QPoint(1, 1));
_windowBorder->resize(_windowWidget->size() + QSize(2, 2));
_windowBorder->show();
// Move button widget to the top right of the window widget
_windowBtnWidget->move(_windowWidget->width() - _windowBtnWidget->width() - 18, 18);
_windowBtnWidget->show();
_windowBtnWidget->raise();
// Set initialized state
_initialized = true;
}
void FramelessWindow::resizeEvent(QResizeEvent* event) {
// Resize window border
if (_windowBorder != nullptr) {
_windowBorder->move(_windowWidget->pos() - QPoint(1, 1));
_windowBorder->resize(_windowWidget->size() + QSize(2, 2));
}
// Resize window mask
QPainterPath path;
path.addRoundedRect(_windowWidget->rect(), _cornerRadius - 1, _cornerRadius - 1);
QRegion region(path.toFillPolygon().toPolygon());
_windowWidget->setMask(region);
// Move button widget to the top right of the window widget
_windowBtnWidget->move(_windowWidget->width() - _windowBtnWidget->width() - 18, 18);
}
void FramelessWindow::controlWindowScale() {
if ((_attributes & LUI_WINDOW_ATTRIBUTES::LUI_WINDOW_DISABLE_MAXIMIZE) != 0) {
return;
}
if (!_maximized) {
_lastWindowGeometry = frameGeometry();
Logger::debug("Maximizing window:");
Logger::debug("[+] current window position: " + std::to_string(x()) + ", " + std::to_string(y()));
Logger::debug("[+] current window size: " + std::to_string(width()) + ", " + std::to_string(height()));
Logger::debug("[+] current geometry: " + std::to_string(_lastWindowGeometry.x()) + ", " + std::to_string(_lastWindowGeometry.y()) + ", " + std::to_string(_lastWindowGeometry.width()) + ", " + std::to_string(_lastWindowGeometry.height()));
Logger::debug("[+] current window widget position: " + std::to_string(_windowWidget->x()) + ", " + std::to_string(_windowWidget->y()));
Logger::debug("[+] current window widget size: " + std::to_string(_windowWidget->width()) + ", " + std::to_string(_windowWidget->height()));
Logger::debug("[+] current window border position: " + std::to_string(_windowBorder->x()) + ", " + std::to_string(_windowBorder->y()));
Logger::debug("[+] current window border size: " + std::to_string(_windowBorder->width()) + ", " + std::to_string(_windowBorder->height()));
_windowShadow->setEnabled(false);
_windowBorder->hide();
QString windowWidgetStyleSheet = "QWidget#windowWidget{background-color:" + _backgroundColor.name(QColor::HexArgb) + ";}";
_windowWidget->setStyleSheet(windowWidgetStyleSheet);
_stretchLayout->setContentsMargins(0, 0, 0, 0);
showMaximized();
QPainterPath path;
path.addRect(_windowWidget->rect());
QRegion mask(path.toFillPolygon().toPolygon());
_windowWidget->setMask(mask);
_maximized = true;
}
else {
_stretchLayout->setContentsMargins(30, 30, 30, 30);
showNormal();
resize(_lastWindowGeometry.size());
move(_lastWindowGeometry.topLeft());
_windowShadow->setEnabled(true);
_windowBorder->show();
QString windowWidgetStyleSheet = "QWidget#windowWidget{background-color:" + _backgroundColor.name(QColor::HexArgb) + ";border-radius:" + QString::number(_cornerRadius) + "px;}";
_windowWidget->setStyleSheet(windowWidgetStyleSheet);
QPainterPath path;
path.addRoundedRect(_windowWidget->rect(), _cornerRadius - 1, _cornerRadius - 1);
QRegion mask(path.toFillPolygon().toPolygon());
_windowWidget->setMask(mask);
Logger::debug("Restoring window:");
Logger::debug("[+] current window position: " + std::to_string(x()) + ", " + std::to_string(y()));
Logger::debug("[+] current window size: " + std::to_string(width()) + ", " + std::to_string(height()));
Logger::debug("[+] current geometry: " + std::to_string(frameGeometry().x()) + ", " + std::to_string(frameGeometry().y()) + ", " + std::to_string(frameGeometry().width()) + ", " + std::to_string(frameGeometry().height()));
Logger::debug("[+] current window widget position: " + std::to_string(_windowWidget->x()) + ", " + std::to_string(_windowWidget->y()));
Logger::debug("[+] current window widget size: " + std::to_string(_windowWidget->width()) + ", " + std::to_string(_windowWidget->height()));
_maximized = false;
}
}
void FramelessWindow::updateMouseState(QMouseEvent* event) {
_mouseState = MOUSE_STATE_NONE;
if ((_attributes & LUI_WINDOW_ATTRIBUTES::LUI_WINDOW_DISABLE_RESIZE) != 0) {
return;
}
if (_maximized) {
setCursor(Qt::ArrowCursor);
return;
}
// Set mouse state according to abs distance from window border
if (abs(event->globalPos().x() - (_windowWidget->frameGeometry().left() + frameGeometry().left())) < 5) {
_mouseState |= MOUSE_STATE_RESIZE_LEFT;
}
if (abs(event->globalPos().x() - (_windowWidget->frameGeometry().right() + frameGeometry().left())) < 5) {
_mouseState |= MOUSE_STATE_RESIZE_RIGHT;
}
if (abs(event->globalPos().y() - (_windowWidget->frameGeometry().top() + frameGeometry().top())) < 5) {
_mouseState |= MOUSE_STATE_RESIZE_TOP;
}
if (abs(event->globalPos().y() - (_windowWidget->frameGeometry().bottom() + frameGeometry().top())) < 5) {
_mouseState |= MOUSE_STATE_RESIZE_BOTTOM;
}
// Set cursor shape according to mouse state
switch (_mouseState) {
case MOUSE_STATE_RESIZE_LEFT:
case MOUSE_STATE_RESIZE_RIGHT:
setCursor(Qt::SizeHorCursor);
break;
case MOUSE_STATE_RESIZE_TOP:
case MOUSE_STATE_RESIZE_BOTTOM:
setCursor(Qt::SizeVerCursor);
break;
case MOUSE_STATE_RESIZE_LEFT | MOUSE_STATE_RESIZE_TOP:
case MOUSE_STATE_RESIZE_RIGHT | MOUSE_STATE_RESIZE_BOTTOM:
setCursor(Qt::SizeFDiagCursor);
break;
case MOUSE_STATE_RESIZE_LEFT | MOUSE_STATE_RESIZE_BOTTOM:
case MOUSE_STATE_RESIZE_RIGHT | MOUSE_STATE_RESIZE_TOP:
setCursor(Qt::SizeBDiagCursor);
break;
default:
setCursor(Qt::ArrowCursor);
break;
}
}
void FramelessWindow::mousePressEvent(QMouseEvent* event) {
if (event->button() == Qt::LeftButton) {
_mousePressed = true;
_lastMousePosition = event->globalPos().toPointF();
}
}
void FramelessWindow::mouseReleaseEvent(QMouseEvent* event) {
_mousePressed = false;
QScreen* screen = QGuiApplication::screenAt(event->globalPos());
Logger::debug("Current screen geometry:");
Logger::debug("[+] screen position: " + std::to_string(screen->geometry().x()) + ", " + std::to_string(screen->geometry().y()));
Logger::debug("[+] screen size: " + std::to_string(screen->geometry().width()) + ", " + std::to_string(screen->geometry().height()));
if (abs(event->globalPos().y() - screen->geometry().top()) < 5) {
controlWindowScale();
}
updateMouseState(event);
}
void FramelessWindow::mouseMoveEvent(QMouseEvent* event) {
Logger::debug("Detected mouse move");
Logger::debug("[+] mouse global position : " + std::to_string(event->globalPos().x()) + ", " + std::to_string(event->globalPos().y()));
Logger::debug("[+] window geometry: " + std::to_string(frameGeometry().x()) + ", " + std::to_string(frameGeometry().y()) + ", " + std::to_string(frameGeometry().width()) + ", " + std::to_string(frameGeometry().height()));
Logger::debug("[+] widget frame geometry: " + std::to_string(_windowWidget->frameGeometry().x()) + ", " + std::to_string(_windowWidget->frameGeometry().y()));
Logger::debug("[+] widget frame size: " + std::to_string(_windowWidget->frameGeometry().width()) + ", " + std::to_string(_windowWidget->frameGeometry().height()));
if (event->buttons() == Qt::NoButton) {
_mousePressed = false;
}
if (abs(event->globalPos().x() - _lastMousePosition.x()) > MAX_MOUSE_MOVEMENT) {
// Maybe moving window across monitors, avoid window disappear
_lastMousePosition = event->globalPos().toPointF();
}
if (abs(event->globalPos().y() - _lastMousePosition.y()) > MAX_MOUSE_MOVEMENT) {
// Maybe moving window across monitors, avoid window disappear
_lastMousePosition = event->globalPos().toPointF();
}
if (!_mousePressed) {
updateMouseState(event);
}
else {
// Resize window according to mouse state
switch (_mouseState) {
case MOUSE_STATE_RESIZE_LEFT:
resize(frameGeometry().width() - event->globalPos().x() + frameGeometry().left() + _windowWidget->frameGeometry().left(), frameGeometry().height());
move(event->globalPos().x() - _windowWidget->frameGeometry().left(), frameGeometry().top());
break;
case MOUSE_STATE_RESIZE_RIGHT:
resize(event->globalPos().x() - frameGeometry().left() + _windowWidget->frameGeometry().left(), frameGeometry().height());
break;
case MOUSE_STATE_RESIZE_TOP:
resize(frameGeometry().width(), frameGeometry().height() - event->globalPos().y() + frameGeometry().top() + _windowWidget->frameGeometry().top());
move(frameGeometry().left(), event->globalPos().y() - _windowWidget->frameGeometry().top());
break;
case MOUSE_STATE_RESIZE_BOTTOM:
resize(frameGeometry().width(), event->globalPos().y() - frameGeometry().top() + _windowWidget->frameGeometry().top());
break;
case MOUSE_STATE_RESIZE_LEFT | MOUSE_STATE_RESIZE_TOP:
resize(frameGeometry().width() - event->globalPos().x() + frameGeometry().left() + _windowWidget->frameGeometry().left(), frameGeometry().height() - event->globalPos().y() + frameGeometry().top() + _windowWidget->frameGeometry().top());
move(event->globalPos().x() - _windowWidget->frameGeometry().left(), event->globalPos().y() - _windowWidget->frameGeometry().top());
break;
case MOUSE_STATE_RESIZE_LEFT | MOUSE_STATE_RESIZE_BOTTOM:
resize(frameGeometry().width() - event->globalPos().x() + frameGeometry().left() + _windowWidget->frameGeometry().left(), event->globalPos().y() - frameGeometry().top() + _windowWidget->frameGeometry().top());
move(event->globalPos().x() - _windowWidget->frameGeometry().left(), frameGeometry().top());
break;
case MOUSE_STATE_RESIZE_RIGHT | MOUSE_STATE_RESIZE_TOP:
resize(event->globalPos().x() - frameGeometry().left() + _windowWidget->frameGeometry().left(), frameGeometry().height() - event->globalPos().y() + frameGeometry().top() + _windowWidget->frameGeometry().top());
move(frameGeometry().left(), event->globalPos().y() - _windowWidget->frameGeometry().top());
break;
case MOUSE_STATE_RESIZE_RIGHT | MOUSE_STATE_RESIZE_BOTTOM:
resize(event->globalPos().x() - frameGeometry().left() + _windowWidget->frameGeometry().left(), event->globalPos().y() - frameGeometry().top() + _windowWidget->frameGeometry().top());
break;
default:
if (_maximized) {
qreal wRatio = (qreal)event->pos().x() / (qreal)_windowWidget->width();
controlWindowScale();
move(event->globalPos().x() - _windowWidget->width() * wRatio, event->globalPos().y() - 52);
}
else {
move(frameGeometry().left() + event->globalPos().x() - _lastMousePosition.x(), frameGeometry().top() + event->globalPos().y() - _lastMousePosition.y());
}
break;
}
_lastMousePosition = event->globalPos().toPointF();
}
}
FramelessWindow::LUI_WINDOW_ATTRIBUTES FramelessWindow::getWindowAttributes() {
return _attributes;
}
void FramelessWindow::setWindowAttributes(unsigned int attributes) {
_attributes = (LUI_WINDOW_ATTRIBUTES)attributes;
if ((_attributes & LUI_WINDOW_ATTRIBUTES::LUI_WINDOW_DISABLE_MINIMIZE) == 0) {
_minimizeBtn->show();
}
else {
_minimizeBtn->hide();
}
if ((_attributes & LUI_WINDOW_ATTRIBUTES::LUI_WINDOW_DISABLE_MAXIMIZE) == 0) {
_maximizeBtn->show();
}
else {
_maximizeBtn->hide();
}
if ((_attributes & LUI_WINDOW_ATTRIBUTES::LUI_WINDOW_DISABLE_CLOSE) == 0) {
_closeBtn->show();
}
else {
_closeBtn->hide();
}
}