From 984c53340c758d4bb116404a2c883fb0c2f245fb Mon Sep 17 00:00:00 2001 From: Linloir <3145078758@qq.com> Date: Wed, 14 Dec 2022 08:41:47 +0800 Subject: [PATCH] UI update: frameless window - window skeleton - multi screen resize support - auto maximize --- FinalProject/main.cpp | 3 + FinalProject/mainwindow.cpp | 320 ++++++++++++++++++++++++++++++++++-- FinalProject/mainwindow.h | 52 ++++++ FinalProject/mainwindow.ui | 25 ++- 4 files changed, 372 insertions(+), 28 deletions(-) diff --git a/FinalProject/main.cpp b/FinalProject/main.cpp index 54a3cdb..fddc5d1 100644 --- a/FinalProject/main.cpp +++ b/FinalProject/main.cpp @@ -5,6 +5,9 @@ int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; + w.setWindowFlag(Qt::FramelessWindowHint); + w.setAttribute(Qt::WA_TranslucentBackground); + w.setMouseTracking(true); w.show(); return a.exec(); } diff --git a/FinalProject/mainwindow.cpp b/FinalProject/mainwindow.cpp index a483fe2..8c67927 100644 --- a/FinalProject/mainwindow.cpp +++ b/FinalProject/mainwindow.cpp @@ -1,24 +1,318 @@ +#include +#include + #include "mainwindow.h" #include "sceneviewer.h" +#include "logger.h" -#include +#define MAX_MOUSE_MOVEMENT 300 MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { ui.setupUi(this); - // Set layout as a horizontal box layout - QHBoxLayout* horizontalLayout = new QHBoxLayout(); - ui.centralWidget->setLayout(horizontalLayout); - // Create a placeholder widget on the left - //QWidget* leftWidget = new QWidget(ui.centralWidget); - //horizontalLayout->addWidget(leftWidget); - // Add sceneviewer widget to the main window - SceneViewer* sceneViewer = new SceneViewer(ui.centralWidget); - horizontalLayout->addWidget(sceneViewer); - sceneViewer->resize(400, 400); + ui.centralWidget->setMouseTracking(true); + // 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); + _windowLayout = new QVBoxLayout(_windowWidget); + _windowLayout->setContentsMargins(0, 0, 0, 0); + _windowLayout->setSpacing(0); + _windowLayout->setAlignment(Qt::AlignTop); + _windowWidget->setLayout(_windowLayout); + _stretchLayout->addWidget(_windowWidget); + _windowWidget->show(); + ui.centralWidget->setLayout(_stretchLayout); + + // Set style sheet for window widget + QString windowWidgetStyleSheet = "QWidget#windowWidget{background-color:" + _backgroundColor.name() + ";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 title bar widget + _titleBar = new QWidget(_windowWidget); + _titleBar->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + _titleBar->setMouseTracking(true); + _titleBarLayout = new QHBoxLayout(_titleBar); + _titleBarLayout->setContentsMargins(18, 18, 18, 18); + _titleBarLayout->setSpacing(0); + _titleBarLayout->setAlignment(Qt::AlignRight | Qt::AlignVCenter); + _titleBar->setLayout(_titleBarLayout); + _windowLayout->addWidget(_titleBar); + _titleBar->show(); + + // Create window control buttons container widget & its layout + _windowBtnWidget = new QWidget(_titleBar); + _windowBtnWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + _windowBtnLayout = new QHBoxLayout(_windowBtnWidget); + _windowBtnLayout->setContentsMargins(0, 0, 0, 0); + _windowBtnLayout->setSpacing(8); + _windowBtnWidget->setLayout(_windowBtnLayout); + _titleBarLayout->addWidget(_windowBtnWidget); + _windowBtnWidget->show(); + + // 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: #828282;}" + "QPushButton:hover{background-color: #e98b2a;}"); + _maximizeBtn->setStyleSheet("QPushButton{border-radius: 6px; background-color: #828282;}" + "QPushButton:hover{background-color: #2d6d4b;}"); + _closeBtn->setStyleSheet("QPushButton{border-radius: 6px; background-color: #828282;}" + "QPushButton:hover{background-color: #ab3b3a;}"); + + _windowBtnLayout->addWidget(_minimizeBtn); + _windowBtnLayout->addWidget(_maximizeBtn); + _windowBtnLayout->addWidget(_closeBtn); + _minimizeBtn->show(); + _maximizeBtn->show(); + _closeBtn->show(); + + // Connect window control buttons + connect(_minimizeBtn, &QPushButton::clicked, this, &MainWindow::showMinimized); + connect(_maximizeBtn, &QPushButton::clicked, this, [=]() {controlWindowScale(); }); + connect(_closeBtn, &QPushButton::clicked, qApp, &QApplication::quit); } -MainWindow::~MainWindow() -{} +MainWindow::~MainWindow() { +} + +void MainWindow::showEvent(QShowEvent* event) { + // Initialize window UI after window is shown + initializeWindowUI(); +} + +void MainWindow::initializeWindowUI() { + // 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() + ";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(); +} + +void MainWindow::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); +} + +void MainWindow::controlWindowScale() { + 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() + ";}"; + _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() + ";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 MainWindow::updateMouseState(QMouseEvent* event) { + _mouseState = MOUSE_STATE_NONE; + if (_maximized) { + 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 MainWindow::mousePressEvent(QMouseEvent* event) { + if (event->button() == Qt::LeftButton) { + _mousePressed = true; + _lastMousePosition = event->globalPos().toPointF(); + } +} + +void MainWindow::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 MainWindow::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(); + } +} diff --git a/FinalProject/mainwindow.h b/FinalProject/mainwindow.h index 8f5372e..8376819 100644 --- a/FinalProject/mainwindow.h +++ b/FinalProject/mainwindow.h @@ -1,6 +1,9 @@ #pragma once #include +#include +#include +#include #include "ui_mainwindow.h" class MainWindow : public QMainWindow @@ -13,4 +16,53 @@ public: private: Ui::MainWindowClass ui; + +private: + // UI control variables + const int _cornerRadius = 20; + const QColor _backgroundColor = QColor(251, 251, 251); + const QColor _borderColor = QColor(104, 104, 104); + + // Window initialize + void initializeWindowUI(); + virtual void showEvent(QShowEvent* event) override; + + // Widget list + QVBoxLayout* _stretchLayout = nullptr; + QWidget* _windowWidget = nullptr; + QWidget* _windowBorder = nullptr; + QGraphicsDropShadowEffect* _windowShadow = nullptr; + QVBoxLayout* _windowLayout = nullptr; + QWidget* _titleBar = nullptr; + QHBoxLayout* _titleBarLayout = nullptr; + QWidget* _windowBtnWidget = nullptr; + QHBoxLayout* _windowBtnLayout = nullptr; + QPushButton* _minimizeBtn = nullptr; + QPushButton* _maximizeBtn = nullptr; + QPushButton* _closeBtn = nullptr; + QWidget* _objectList = nullptr; + QWidget* _settingsPannel = nullptr; + + // Window size control + bool _maximized = false; + QRect _lastWindowGeometry; + + virtual void resizeEvent(QResizeEvent* event) override; + void controlWindowScale(); + + // User interaction control + enum MOUSE_STATE { + MOUSE_STATE_NONE = 0, + MOUSE_STATE_RESIZE_LEFT = 1 << 0, + MOUSE_STATE_RESIZE_TOP = 1 << 1, + MOUSE_STATE_RESIZE_RIGHT = 1 << 2, + MOUSE_STATE_RESIZE_BOTTOM = 1 << 3 + }; + bool _mousePressed = false; + int _mouseState = MOUSE_STATE_NONE; + QPointF _lastMousePosition; + void updateMouseState(QMouseEvent* event); + virtual void mousePressEvent(QMouseEvent* event) override; + virtual void mouseReleaseEvent(QMouseEvent* event) override; + virtual void mouseMoveEvent(QMouseEvent* event) override; }; diff --git a/FinalProject/mainwindow.ui b/FinalProject/mainwindow.ui index 019493c..444ab27 100644 --- a/FinalProject/mainwindow.ui +++ b/FinalProject/mainwindow.ui @@ -1,10 +1,8 @@ - + + MainWindowClass - - - MainWindowClass - - + + 0 0 @@ -12,17 +10,14 @@ 400 - + MainWindow - - - - + + - - + - + - +