diff --git a/FinalProject/FinalProject.vcxproj b/FinalProject/FinalProject.vcxproj index 75e1a28..f3f0de1 100644 --- a/FinalProject/FinalProject.vcxproj +++ b/FinalProject/FinalProject.vcxproj @@ -103,23 +103,32 @@ + + + + + + + + + + - @@ -128,18 +137,28 @@ + + + + + + + + + + - - + + diff --git a/FinalProject/FinalProject.vcxproj.filters b/FinalProject/FinalProject.vcxproj.filters index 20d5f67..7638a20 100644 --- a/FinalProject/FinalProject.vcxproj.filters +++ b/FinalProject/FinalProject.vcxproj.filters @@ -13,10 +13,6 @@ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} qrc;rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - {99349809-55BA-4b9d-BF79-8FDBB0286EB3} - ui - {639EADAA-A684-42e4-A9AD-28FC9BCB8F7C} ts @@ -39,14 +35,53 @@ {602fc1ed-84a6-4d4a-ad71-5e7415d8d429} + + {dd0c6d73-36ab-44ac-9780-4c378faa5774} + + + {d0eeae28-809f-4e8e-b1e0-d5d7c5cca3e7} + + + {0ee9dd70-07d0-44ec-9cb5-37478f6d9ede} + + + {c6368565-9e0a-487b-953c-6c4bc9e3f655} + + + {2ce6e2ec-f0bf-4d49-8b11-cdf0f31693c5} + + + {f8b17ddd-9689-45cc-84a1-5c321b0290b0} + + + {469e46b8-328a-474b-bf82-19c86b980e82} + + + {1351d841-7665-48d3-9850-da0738823396} + + + {5e6e1e89-1c77-4a89-92cc-d64ea2546961} + + + {d2b9abbc-ef10-4990-96a1-a88f9c217dcf} + + + {d6a38868-baed-45d5-9cbf-3faa3b563fa9} + + + {4b5e61f5-3dfe-44d7-bfcc-cf5b70528b16} + + + {995296ff-ac0c-4996-9692-c0e017e5fc03} + + + {6c5ac0df-26ad-40ad-860c-3d0372b81094} + Resource Files - - Form Files - @@ -85,21 +120,51 @@ Source Files\Qt Widgets - - Source Files\Qt Widgets - - - Source Files\Qt Widgets - - - Source Files\Qt Widgets - - - Source Files\Qt Widgets - Source Files\Utils + + Source Files\Qt Widgets\Pages\Scene Editor + + + Source Files\Qt Widgets\GUI Components + + + Source Files\Qt Widgets\GUI Components + + + Source Files\Qt Widgets\GUI Components + + + Source Files\Qt Widgets\GUI Components + + + Source Files\Qt Widgets\GUI Components + + + Source Files\Qt Widgets\GUI Components + + + Source Files\Qt Widgets\Pages\About Page + + + Source Files\Qt Widgets\GUI Components + + + Source Files\Qt Widgets\Pages\Scene Editor\Object Selector + + + Source Files\Qt Widgets\Pages\Scene Editor\Object Selector + + + Source Files\Qt Widgets\Pages\Scene Editor\Object Setter + + + Source Files\Qt Widgets\Pages\Scene Editor\Scene Viewer + + + Source Files\Qt Widgets\Pages\Scene Editor\Object Selector + @@ -117,12 +182,6 @@ Header Files\OpenGL Abstractions - - Header Files\Qt Widgets - - - Header Files\Qt Widgets - Header Files\OpenGL Abstractions @@ -141,20 +200,56 @@ Header Files\Utils - - Header Files\Qt Widgets - Header Files\Utils + + Header Files\Qt Widgets\Pages\Scene Editor\Object Setter + - - Header Files\Qt Widgets - Header Files\Qt Widgets + + Header Files\Qt Widgets\GUI Components + + + Header Files\Qt Widgets\GUI Components + + + Header Files\Qt Widgets\GUI Components + + + Header Files\Qt Widgets\GUI Components + + + Header Files\Qt Widgets\Pages\Scene Editor + + + Header Files\Qt Widgets\GUI Components + + + Header Files\Qt Widgets\Pages\About Page + + + Header Files\Qt Widgets\GUI Components + + + Header Files\Qt Widgets\GUI Components + + + Header Files\Qt Widgets\Pages\Scene Editor\Scene Viewer + + + Header Files\Qt Widgets\Pages\Scene Editor\Object Selector + + + Header Files\Qt Widgets\Pages\Scene Editor\Object Selector + + + Header Files\Qt Widgets\Pages\Scene Editor\Object Selector + diff --git a/FinalProject/aboutpage.cpp b/FinalProject/aboutpage.cpp new file mode 100644 index 0000000..62b2631 --- /dev/null +++ b/FinalProject/aboutpage.cpp @@ -0,0 +1,135 @@ +#include + +#include "aboutpage.h" + +AboutPage::AboutPage(QWidget* parent) : + PageWidget(parent) +{ + _contentWidget->setMouseTracking(true); + + // Construct title layout + _titleLayout = new QVBoxLayout(_contentWidget); + _titleLayout->setContentsMargins(28, 46, 28, 28); + _titleLayout->setSpacing(18); + _titleLayout->setAlignment(Qt::AlignTop | Qt::AlignLeft); + _contentWidget->setLayout(_titleLayout); + + // Construct title + _titleLabel = new QLabel("ABOUT", _contentWidget); + _titleLabel->setFont(_titleFont); + _titleLayout->addWidget(_titleLabel); + _titleLabel->show(); + + // Construct main layout + _mainWidget = new QWidget(_contentWidget); + _mainWidget->setObjectName("mainWidget"); + _mainWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); + _mainWidget->setStyleSheet("QWidget#mainWidget { background-color: #efefef; border-radius: 8px; }"); + _mainLayout = new QVBoxLayout(_mainWidget); + _mainLayout->setAlignment(Qt::AlignTop | Qt::AlignLeft); + _mainLayout->setContentsMargins(0, 8, 0, 8); + _mainLayout->setSpacing(8); + _mainWidget->setLayout(_mainLayout); + _titleLayout->addWidget(_mainWidget); + _mainWidget->show(); + + // Construct contents + _authorLabel = new LineEditWidget(_mainWidget); + _authorLabel->mainLayout()->insertWidget(0, new QLabel("Author")); + _authorLabel->mainLayout()->insertSpacing(1, 8); + _authorLabel->setBackgroundColor(QColor(0, 0, 0, 0)); + _authorLabel->setHoverColor(QColor(0, 0, 0, 10)); + _authorLabel->setPressedColor(QColor(0, 0, 0, 20)); + _authorLabel->setText("Linloir"); + _authorLabel->setEnabled(false); + _mainLayout->addWidget(_authorLabel); + _authorLabel->show(); + + _versionLabel = new LineEditWidget(_mainWidget); + _versionLabel->mainLayout()->insertWidget(0, new QLabel("Version")); + _versionLabel->mainLayout()->insertSpacing(1, 8); + _versionLabel->setBackgroundColor(QColor(0, 0, 0, 0)); + _versionLabel->setHoverColor(QColor(0, 0, 0, 10)); + _versionLabel->setPressedColor(QColor(0, 0, 0, 20)); + _versionLabel->setText("1.0.0"); + _versionLabel->setEnabled(false); + _mainLayout->addWidget(_versionLabel); + _versionLabel->show(); + + _dateLabel = new LineEditWidget(_mainWidget); + _dateLabel->mainLayout()->insertWidget(0, new QLabel("Last Update")); + _dateLabel->mainLayout()->insertSpacing(1, 8); + _dateLabel->setBackgroundColor(QColor(0, 0, 0, 0)); + _dateLabel->setHoverColor(QColor(0, 0, 0, 10)); + _dateLabel->setPressedColor(QColor(0, 0, 0, 20)); + _dateLabel->setText("2020/12/31"); + _dateLabel->setEnabled(false); + _mainLayout->addWidget(_dateLabel); + _dateLabel->show(); +} + +AboutPage::~AboutPage() {} + +PushButton* AboutPage::getPageIconButton(QWidget* context) { + // Check for existed button + if (_iconButton != nullptr) { + return _iconButton; + } + + // Generate new icon button + _iconButton = new PushButton(nullptr, context); + _iconButton->setMargin(20, 18, 16, 18); + _iconButtonLabel = new QLabel(_iconButton); + _iconButtonLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); + QFont iconButtonFont("Font Awesome 6 Free Regular", 12); + iconButtonFont.setStyleStrategy(QFont::PreferAntialias); + _iconButtonLabel->setFont(iconButtonFont); + _iconButtonLabel->setText("\uf059"); + _iconButtonLabel->setAlignment(Qt::AlignLeft); + _iconButton->setChildWidget(_iconButtonLabel); + + // Return newly generated icon + return _iconButton; +} + +PushButton* AboutPage::getPageTextButton(QWidget* context) { + // Check for existed button + if (_textButton != nullptr) { + return _textButton; + } + + // Generate new text button + _textButton = new PushButton(nullptr, context); + _textButton->setMargin(20, 18, 16, 18); + _textButtonWidget = new QWidget(_textButton); + _textButtonLayout = new QHBoxLayout(_textButtonWidget); + _textButtonLayout->setContentsMargins(0, 0, 0, 0); + _textButtonLayout->setSpacing(12); + _textButtonWidget->setLayout(_textButtonLayout); + + // Generate text button contents + _textButtonIcon = new QLabel(_textButtonWidget); + QFont textButtonFont("Font Awesome 6 Free Regular", 12); + textButtonFont.setStyleStrategy(QFont::PreferQuality); + _textButtonIcon->setFont(textButtonFont); + _textButtonIcon->setText("\uf059"); // set icon to "pen" icon + _textButtonIcon->setAlignment(Qt::AlignLeft); + + _textButtonLabel = new QLabel(_textButtonWidget); + _textButtonLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); + _textButtonLabel->setText("About"); + _textButtonLabel->setAlignment(Qt::AlignLeft); + + // Add text button contents to layout + _textButtonLayout->addWidget(_textButtonIcon); + _textButtonLayout->addWidget(_textButtonLabel); + _textButtonIcon->show(); + _textButtonLabel->show(); + + // Set text button child widget + _textButton->setChildWidget(_textButtonWidget); + _textButtonWidget->show(); + + // Return newly generated text button + return _textButton; +} diff --git a/FinalProject/aboutpage.h b/FinalProject/aboutpage.h new file mode 100644 index 0000000..3a242d6 --- /dev/null +++ b/FinalProject/aboutpage.h @@ -0,0 +1,41 @@ +#pragma once + +#include +#include +#include + +#include "pagewidget.h" +#include "lineeditwidget.h" + +class AboutPage : public PageWidget { + + Q_OBJECT + +public: + AboutPage(QWidget* parent = 0); + ~AboutPage(); + +private: + // Push button icons + QLabel* _iconButtonLabel = nullptr; + QWidget* _textButtonWidget = nullptr; + QHBoxLayout* _textButtonLayout = nullptr; + QLabel* _textButtonIcon = nullptr; + QLabel* _textButtonLabel = nullptr; + + // UI elements + QVBoxLayout* _titleLayout = nullptr; + const QFont _titleFont = QFont("DengXian", 26, QFont::ExtraLight); + QLabel* _titleLabel = nullptr; + + QWidget* _mainWidget = nullptr; + QVBoxLayout* _mainLayout = nullptr; + + LineEditWidget* _authorLabel = nullptr; + LineEditWidget* _versionLabel = nullptr; + LineEditWidget* _dateLabel = nullptr; + +public: + virtual PushButton* getPageIconButton(QWidget* context) override; + virtual PushButton* getPageTextButton(QWidget* context) override; +}; diff --git a/FinalProject/editorpage.cpp b/FinalProject/editorpage.cpp new file mode 100644 index 0000000..a10bc68 --- /dev/null +++ b/FinalProject/editorpage.cpp @@ -0,0 +1,118 @@ +#pragma once + +#include + +#include "editorpage.h" + +EditorPage::EditorPage(QWidget* parent) : + PageWidget(parent) +{ + _contentWidget->setMouseTracking(true); + + // Construct title layout + _titleLayout = new QVBoxLayout(_contentWidget); + _titleLayout->setContentsMargins(28, 46, 28, 28); + _titleLayout->setSpacing(18); + _titleLayout->setAlignment(Qt::AlignTop | Qt::AlignLeft); + _contentWidget->setLayout(_titleLayout); + + // Construct title + _titleLabel = new QLabel("EDITOR", _contentWidget); + _titleLabel->setFont(_titleFont); + _titleLayout->addWidget(_titleLabel); + _titleLabel->show(); + + // Construct main layout + _mainWidget = new QWidget(_contentWidget); + _mainWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + _mainLayout = new QHBoxLayout(_mainWidget); + _mainLayout->setContentsMargins(0, 0, 0, 0); + _mainLayout->setSpacing(16); + _mainWidget->setLayout(_mainLayout); + _titleLayout->addWidget(_mainWidget); + _mainWidget->show(); + + // Generate model selector + _modelSelector = new ModelSelector(_mainWidget); + _mainLayout->addWidget(_modelSelector); + _modelSelector->show(); + + // Generate scene viewer + _sceneViewerContainer = new RoundedCornerWidget(_mainWidget); + _sceneViewerContainer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + _sceneViewerContainerLayout = new QVBoxLayout(_sceneViewerContainer->mainWidget()); + _sceneViewerContainerLayout->setContentsMargins(0, 0, 0, 0); + _sceneViewerContainerLayout->setSpacing(0); + _sceneViewerContainer->mainWidget()->setLayout(_sceneViewerContainerLayout); + _sceneViewer = new SceneViewer(_sceneViewerContainer->mainWidget()); + _sceneViewerContainerLayout->addWidget(_sceneViewer); + _sceneViewer->show(); + _mainLayout->addWidget(_sceneViewerContainer); + _sceneViewerContainer->show(); +} + +EditorPage::~EditorPage() {} + +PushButton* EditorPage::getPageIconButton(QWidget* context) { + // Check for existed button + if (_iconButton != nullptr) { + return _iconButton; + } + + // Generate new icon button + _iconButton = new PushButton(nullptr, context); + _iconButton->setMargin(20, 18, 16, 18); + _iconButtonLabel = new QLabel(_iconButton); + _iconButtonLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); + QFont iconButtonFont("Font Awesome 6 Free Solid", 12); + iconButtonFont.setStyleStrategy(QFont::PreferAntialias); + _iconButtonLabel->setFont(iconButtonFont); + _iconButtonLabel->setText("\uf304"); // set icon to "pen" icon + _iconButtonLabel->setAlignment(Qt::AlignLeft); + _iconButton->setChildWidget(_iconButtonLabel); + + // Return newly generated icon + return _iconButton; +} + +PushButton* EditorPage::getPageTextButton(QWidget* context) { + // Check for existed button + if (_textButton != nullptr) { + return _textButton; + } + + // Generate new text button + _textButton = new PushButton(nullptr, context); + _textButton->setMargin(20, 18, 16, 18); + _textButtonWidget = new QWidget(_textButton); + _textButtonLayout = new QHBoxLayout(_textButtonWidget); + _textButtonLayout->setContentsMargins(0, 0, 0, 0); + _textButtonLayout->setSpacing(12); + _textButtonWidget->setLayout(_textButtonLayout); + + // Generate text button contents + _textButtonIcon = new QLabel(_textButtonWidget); + QFont textButtonFont("Font Awesome 6 Free Solid", 12); + textButtonFont.setStyleStrategy(QFont::PreferQuality); + _textButtonIcon->setFont(textButtonFont); + _textButtonIcon->setText("\uf304"); // set icon to "pen" icon + _textButtonIcon->setAlignment(Qt::AlignLeft); + + _textButtonLabel = new QLabel(_textButtonWidget); + _textButtonLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); + _textButtonLabel->setText("Editor"); + _textButtonLabel->setAlignment(Qt::AlignLeft); + + // Add text button contents to layout + _textButtonLayout->addWidget(_textButtonIcon); + _textButtonLayout->addWidget(_textButtonLabel); + _textButtonIcon->show(); + _textButtonLabel->show(); + + // Set text button child widget + _textButton->setChildWidget(_textButtonWidget); + _textButtonWidget->show(); + + // Return newly generated text button + return _textButton; +} diff --git a/FinalProject/editorpage.h b/FinalProject/editorpage.h new file mode 100644 index 0000000..acf41cf --- /dev/null +++ b/FinalProject/editorpage.h @@ -0,0 +1,45 @@ +#pragma once + +#include +#include +#include + +#include "pagewidget.h" +#include "sceneviewer.h" +#include "roundedcornerwidget.h" +#include "modelselector.h" + +class EditorPage : public PageWidget { + + Q_OBJECT + +public: + EditorPage(QWidget* parent = 0); + ~EditorPage(); + +private: + // Push button icons + QLabel* _iconButtonLabel = nullptr; + QWidget* _textButtonWidget = nullptr; + QHBoxLayout* _textButtonLayout = nullptr; + QLabel* _textButtonIcon = nullptr; + QLabel* _textButtonLabel = nullptr; + + // UI elements + QVBoxLayout* _titleLayout = nullptr; + const QFont _titleFont = QFont("DengXian", 26, QFont::ExtraLight); + QLabel* _titleLabel = nullptr; + + QWidget* _mainWidget = nullptr; + QHBoxLayout* _mainLayout = nullptr; + + ModelSelector* _modelSelector = nullptr; + + RoundedCornerWidget* _sceneViewerContainer = nullptr; + QVBoxLayout* _sceneViewerContainerLayout = nullptr; + SceneViewer* _sceneViewer = nullptr; + +public: + virtual PushButton* getPageIconButton(QWidget* context) override; + virtual PushButton* getPageTextButton(QWidget* context) override; +}; diff --git a/FinalProject/font_awesome_6_regular_free.otf b/FinalProject/font_awesome_6_regular_free.otf new file mode 100644 index 0000000..5c3bf98 Binary files /dev/null and b/FinalProject/font_awesome_6_regular_free.otf differ diff --git a/FinalProject/font_awesome_6_solid_free.otf b/FinalProject/font_awesome_6_solid_free.otf new file mode 100644 index 0000000..6851a08 Binary files /dev/null and b/FinalProject/font_awesome_6_solid_free.otf differ diff --git a/FinalProject/framelesswindow.cpp b/FinalProject/framelesswindow.cpp new file mode 100644 index 0000000..bd77c39 --- /dev/null +++ b/FinalProject/framelesswindow.cpp @@ -0,0 +1,373 @@ +#include +#include +#include +#include + +#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(); + } +} diff --git a/FinalProject/framelesswindow.h b/FinalProject/framelesswindow.h new file mode 100644 index 0000000..bfebee1 --- /dev/null +++ b/FinalProject/framelesswindow.h @@ -0,0 +1,82 @@ +#pragma once + +#include +#include +#include +#include +#include + +class FramelessWindow : public QWidget { + + Q_OBJECT + +public: + // Window attributes + enum LUI_WINDOW_ATTRIBUTES { + LUI_WINDOW_NO_ATTRIBUTES = 0, + LUI_WINDOW_DISABLE_CLOSE = 1 << 0, + LUI_WINDOW_DISABLE_MAXIMIZE = 1 << 1, + LUI_WINDOW_DISABLE_MINIMIZE = 1 << 2, + LUI_WINDOW_DISABLE_RESIZE = 1 << 3 + }; + +public: + FramelessWindow(QWidget* parent = 0); + FramelessWindow(int cornerRadius, QWidget* parent = 0); + FramelessWindow(unsigned int attributes, QWidget* parent = 0); + FramelessWindow(int cornerRadius, unsigned int attributes, QWidget* parent = 0); + ~FramelessWindow(); + +protected: + // UI control variables + const int _cornerRadius = 0; + const QColor _backgroundColor = QColor(251, 251, 251); + const QColor _borderColor = QColor(104, 104, 104); + + // Window initialize + bool _initialized = false; // prevent double initialization when restore from minimized state + void initializeWindowUI(); + virtual void showEvent(QShowEvent* event) override; + + // Widget list + QVBoxLayout* _stretchLayout = nullptr; + + QWidget* _windowWidget = nullptr; + QWidget* _windowBorder = nullptr; + QGraphicsDropShadowEffect* _windowShadow = nullptr; + + QWidget* _windowBtnWidget = nullptr; + QHBoxLayout* _windowBtnLayout = nullptr; + QPushButton* _minimizeBtn = nullptr; + QPushButton* _maximizeBtn = nullptr; + QPushButton* _closeBtn = nullptr; + + // Window size control + LUI_WINDOW_ATTRIBUTES _attributes = LUI_WINDOW_NO_ATTRIBUTES; + 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; + +public: + QWidget* windowWidget() const { return _windowWidget; } + LUI_WINDOW_ATTRIBUTES getWindowAttributes(); + void setWindowAttributes(unsigned int attributes); +}; \ No newline at end of file diff --git a/FinalProject/lineeditwidget.cpp b/FinalProject/lineeditwidget.cpp new file mode 100644 index 0000000..ca4ec86 --- /dev/null +++ b/FinalProject/lineeditwidget.cpp @@ -0,0 +1,427 @@ +#include +#include + +#include "lineeditwidget.h" + +LineEditWidget::LineEditWidget(QWidget* parent) : + QWidget(parent) +{ + // Generate colors + generateColor(_defaultColorScheme); + + // Initialize ui + initializeUI(); + + // Connect line edit events + connect(_editor, &QLineEdit::returnPressed, this, &LineEditWidget::endEdit); + connect(_editor, &QLineEdit::editingFinished, this, &LineEditWidget::endEdit); + connect(_editor, &QLineEdit::textChanged, this, &LineEditWidget::onTextChanged); +} + +LineEditWidget::~LineEditWidget() { +} + +void LineEditWidget::initializeUI() { + // Construct and set main layout + _mainLayout = new QHBoxLayout(this); + _mainLayout->setContentsMargins(12, 0, 12, 0); + _mainLayout->setSpacing(8); + setLayout(_mainLayout); + + // Construct editor container widget + _editorWidget = new QWidget(this); + _mainLayout->addWidget(_editorWidget); + _editorWidget->show(); + + // Construct editor layout to stretch editor widget + _editorWidgetLayout = new QHBoxLayout(_editorWidget); + _editorWidgetLayout->setContentsMargins(0, 10, 0, 10); + _editorWidgetLayout->setSpacing(0); + _editorWidget->setLayout(_editorWidgetLayout); + + // Install event filter to editor widget to resize indicator + _editorWidget->installEventFilter(this); + + // Construct real text editor + _editor = new QLineEdit(_editorWidget); + _editor->setText(""); + _editor->setFont(_defaultFont); + _editor->setAlignment(Qt::AlignRight); + _editor->setReadOnly(true); + _editor->setAttribute(Qt::WA_TransparentForMouseEvents, true); + _editor->setStyleSheet("QLineEdit{color:#2c2c2c;background-color:#00ffffff;border-style:none;}"); + //_editor->setFixedHeight(_editor->fontMetrics().lineSpacing()); // Restrict one line + _editor->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum); + _editorWidgetLayout->addWidget(_editor); + _editor->show(); + + // Construct indicator widget + _indicator = new QWidget(_editorWidget); + _indicator->setObjectName("indicator"); + _indicator->setStyleSheet("QWidget#indicator{background-color:" + _indicatorColor.name(QColor::HexArgb) + ";" + "border-radius:" + QString::number((float)_indicatorWidth / 2) + "px;}"); + _indicator->resize(_indicatorWidth, _indicatorWidth); + _indicator->move(_editorWidget->width() - _indicatorWidth, _editorWidget->height() - _indicatorWidth - _indicatorSpacing); + _indicatorEffect = new QGraphicsOpacityEffect(_indicator); + _indicatorEffect->setOpacity(0); + _indicator->setGraphicsEffect(_indicatorEffect); + _indicator->show(); + + // Construct background widget + _backgroundWidget = new QWidget(this); + _backgroundWidget->resize(size()); + _backgroundWidget->setObjectName("backgroundWidget"); + _backgroundWidget->setStyleSheet("QWidget#backgroundWidget{background-color:" + _backgroundColor.name(QColor::HexArgb) + ";" + "border-radius:" + QString::number(_cornerRadius) + "px;}"); + _backgroundWidget->lower(); + _backgroundWidget->show(); + + // Set size policy + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); +} + +void LineEditWidget::generateColor(QColor colorScheme) { + _backgroundColor = colorScheme.lighter(120); + _backgroundColor.setAlpha(5); + _hoverColor = colorScheme.lighter(120); + _hoverColor.setAlpha(40); + _pressedColor = colorScheme.lighter(120); + _pressedColor.setAlpha(50); + _indicatorColor = colorScheme; +} + +void LineEditWidget::startEdit() { + if (_editing) { + return; + } + + // Set editing flag + _editing = true; + + // Enable qlineedit widget + _editor->setReadOnly(false); + _editor->setAttribute(Qt::WA_TransparentForMouseEvents, false); + _editor->setFocus(); + _editor->setCursorPosition(_editor->text().length()); + + // Minorly move cursor to update cursor icon + QCursor::setPos(QCursor::pos() + QPoint(1, 0)); + QCursor::setPos(QCursor::pos() + QPoint(-1, 0)); + + // Add grow and fade in animation for indicator + QParallelAnimationGroup* startEditAnimation = new QParallelAnimationGroup(this); + QPropertyAnimation* growAnimation = new QPropertyAnimation(_indicator, "geometry"); + QPropertyAnimation* fadeInAnimation = new QPropertyAnimation(_indicatorEffect, "opacity"); + growAnimation->setDuration(200); + growAnimation->setEasingCurve(QEasingCurve::OutQuad); + fadeInAnimation->setDuration(200); + fadeInAnimation->setEasingCurve(QEasingCurve::OutQuad); + growAnimation->setStartValue(_indicator->geometry()); + growAnimation->setEndValue(QRect( + 0, + _editorWidget->height() - _indicatorWidth - _indicatorSpacing, + _editorWidget->width(), + _indicatorWidth + )); + fadeInAnimation->setStartValue(_indicatorEffect->opacity()); + fadeInAnimation->setEndValue(0.999); + startEditAnimation->addAnimation(growAnimation); + startEditAnimation->addAnimation(fadeInAnimation); + startEditAnimation->start(QAbstractAnimation::DeleteWhenStopped); + + // Emit signal + emit onStartEditing(_editor->text()); +} + +void LineEditWidget::endEdit() { + if (!_editing) { + return; + } + + // Set editing flag + _editing = false; + + // Disable qlineedit widget + _editor->setReadOnly(true); + _editor->setAttribute(Qt::WA_TransparentForMouseEvents, true); + _editor->setSelection(0, 0); + + // Add shrink and fade out animation for indicator + QParallelAnimationGroup* endEditAnimation = new QParallelAnimationGroup(this); + QPropertyAnimation* shrinkAnimation = new QPropertyAnimation(_indicator, "geometry"); + QPropertyAnimation* fadeOutAnimation = new QPropertyAnimation(_indicatorEffect, "opacity"); + shrinkAnimation->setDuration(200); + shrinkAnimation->setEasingCurve(QEasingCurve::OutQuad); + fadeOutAnimation->setDuration(200); + fadeOutAnimation->setEasingCurve(QEasingCurve::OutQuad); + shrinkAnimation->setStartValue(_indicator->geometry()); + shrinkAnimation->setEndValue(QRect( + _editorWidget->width() - _indicatorWidth, + _editorWidget->height() - _indicatorWidth - _indicatorSpacing, + _indicatorWidth, + _indicatorWidth + )); + fadeOutAnimation->setStartValue(_indicatorEffect->opacity()); + fadeOutAnimation->setEndValue(0); + endEditAnimation->addAnimation(shrinkAnimation); + endEditAnimation->addAnimation(fadeOutAnimation); + endEditAnimation->start(QAbstractAnimation::DeleteWhenStopped); + + // Emit signal + emit onEndEditing(_editor->text()); +} + +void LineEditWidget::showEvent(QShowEvent* event) { + // Call on parent + QWidget::showEvent(event); + + // Check initialize state + if (_initialized) { + return; + } + + // Initialize size dependent widgets + _backgroundWidget->resize(size()); + if (_editing) { + _indicator->move(0, _editorWidget->height() - _indicatorWidth - _indicatorSpacing); + _indicator->resize(_editorWidget->width(), _indicatorWidth); + } + else { + _indicator->move(_editorWidget->width() - _indicatorWidth, _editorWidget->height() - _indicatorWidth - _indicatorSpacing); + _indicator->resize(_indicatorWidth, _indicatorWidth); + } + + // Set initialized flag + _initialized = true; +} + +void LineEditWidget::enterEvent(QEnterEvent* event) { + // Check on enabled + if (_enabled) { + setCursor(Qt::PointingHandCursor); + } + + // Change background color + _backgroundWidget->setStyleSheet("QWidget#backgroundWidget{background-color:" + _hoverColor.name(QColor::HexArgb) + ";" + "border-radius:" + QString::number(_cornerRadius) + "px;}"); + + // Set hover flag + _hovered = true; +} + +void LineEditWidget::leaveEvent(QEvent* event) { + setCursor(Qt::ArrowCursor); + + // Change background color + _backgroundWidget->setStyleSheet("QWidget#backgroundWidget{background-color:" + _backgroundColor.name(QColor::HexArgb) + ";" + "border-radius:" + QString::number(_cornerRadius) + "px;}"); + + // Set hover flag + _hovered = false; + _pressed = false; +} + +void LineEditWidget::mousePressEvent(QMouseEvent* event) { + // Change background color + _backgroundWidget->setStyleSheet("QWidget#backgroundWidget{background-color:" + _pressedColor.name(QColor::HexArgb) + ";" + "border-radius:" + QString::number(_cornerRadius) + "px;}"); + + // Set pressed flag + _pressed = true; +} + +void LineEditWidget::mouseReleaseEvent(QMouseEvent* event) { + // Change background color + _backgroundWidget->setStyleSheet("QWidget#backgroundWidget{background-color:" + _hoverColor.name(QColor::HexArgb) + ";" + "border-radius:" + QString::number(_cornerRadius) + "px;}"); + + // Trigger on click + if (_pressed) { + if (_enabled) { + if (_editing) { + endEdit(); + } + else { + startEdit(); + } + } + else { + if (_editing) { + endEdit(); + } + } + } + + // Set pressed flag + _pressed = false; +} + +void LineEditWidget::focusInEvent(QFocusEvent* event) { + // Call on parent + QWidget::focusInEvent(event); + + // Check on enabled + if (!_enabled) { + return; + } + + // Change background color + _backgroundWidget->setStyleSheet("QWidget#backgroundWidget{background-color:" + _hoverColor.name(QColor::HexArgb) + ";" + "border-radius:" + QString::number(_cornerRadius) + "px;}"); + + // Start edit + if (!_editing) { + startEdit(); + } +} + +void LineEditWidget::focusOutEvent(QFocusEvent* event) { + // Call on parent + QWidget::focusOutEvent(event); + + // Change background color + _backgroundWidget->setStyleSheet("QWidget#backgroundWidget{background-color:" + _backgroundColor.name(QColor::HexArgb) + ";" + "border-radius:" + QString::number(_cornerRadius) + "px;}"); + + // End edit + if (_editing) { + endEdit(); + } +} + +void LineEditWidget::resizeEvent(QResizeEvent* event) { + // Check initialize state + if (!_initialized) { + return; + } + + // Resize background widget + _backgroundWidget->resize(size()); +} + +bool LineEditWidget::eventFilter(QObject* object, QEvent* event) { + // Resize indicator when editor widget size changed + if (object == _editorWidget) { + if (event->type() == QEvent::Resize) { + if (_editing) { + _indicator->move(0, _editorWidget->height() - _indicatorWidth - _indicatorSpacing); + _indicator->resize(_editorWidget->width(), _indicatorWidth); + } + else { + _indicator->move(_editorWidget->width() - _indicatorWidth, _editorWidget->height() - _indicatorWidth - _indicatorSpacing); + _indicator->resize(_indicatorWidth, _indicatorWidth); + } + } + } + + // Call on parent + return QWidget::eventFilter(object, event); +} + +void LineEditWidget::setText(const QString& text) { + // Set text + _editor->setText(text); +} + +void LineEditWidget::setPlaceholderText(const QString& text) { + // Set placeholder text + _editor->setPlaceholderText(text); +} + +void LineEditWidget::setValidator(const QValidator* validator) { + // Set validator + _editor->setValidator(validator); +} + +void LineEditWidget::setEnabled(bool enabled) { + // Set enabled + _enabled = enabled; + + // Check for current state + if (_editing && !_enabled) { + endEdit(); + } +} + +void LineEditWidget::setMargins(QMargins margins) { + // Set margins + _mainLayout->setContentsMargins(margins); +} + +void LineEditWidget::setMargins(int left, int top, int right, int bottom) { + // Set margins + _mainLayout->setContentsMargins(left, top, right, bottom); +} + +void LineEditWidget::setBackgroundColor(QColor color) { + // Set background color + _backgroundColor = color; + + // Check for current state + if (!_hovered && !_pressed) { + _backgroundWidget->setStyleSheet("QWidget#backgroundWidget{background-color:" + _backgroundColor.name(QColor::HexArgb) + ";" + "border-radius:" + QString::number(_cornerRadius) + "px;}"); + } +} + +void LineEditWidget::setHoverColor(QColor color) { + // Set hover color + _hoverColor = color; + + // Check for current state + if (_hovered && !_pressed) { + _backgroundWidget->setStyleSheet("QWidget#backgroundWidget{background-color:" + _hoverColor.name(QColor::HexArgb) + ";" + "border-radius:" + QString::number(_cornerRadius) + "px;}"); + } +} + +void LineEditWidget::setPressedColor(QColor color) { + // Set pressed color + _pressedColor = color; + + // Check for current state + if (_pressed) { + _backgroundWidget->setStyleSheet("QWidget#backgroundWidget{background-color:" + _pressedColor.name(QColor::HexArgb) + ";" + "border-radius:" + QString::number(_cornerRadius) + "px;}"); + } +} + +void LineEditWidget::setIndicatorColor(QColor color) { + // Set indicator color + _indicatorColor = color; + + _indicator->setStyleSheet("QWidget#indicator{background-color:" + _indicatorColor.name(QColor::HexArgb) + ";" + "border-radius:" + QString::number(_indicatorWidth) + "px;}"); +} + +void LineEditWidget::setColorScheme(QColor primaryColor) { + // Generate colors + generateColor(primaryColor); + + // Check for current state + if (!_hovered && !_pressed) { + _backgroundWidget->setStyleSheet("QWidget#backgroundWidget{background-color:" + _backgroundColor.name(QColor::HexArgb) + ";" + "border-radius:" + QString::number(_cornerRadius) + "px;}"); + } + else if (_hovered && !_pressed) { + _backgroundWidget->setStyleSheet("QWidget#backgroundWidget{background-color:" + _hoverColor.name(QColor::HexArgb) + ";" + "border-radius:" + QString::number(_cornerRadius) + "px;}"); + } + else if (_pressed) { + _backgroundWidget->setStyleSheet("QWidget#backgroundWidget{background-color:" + _pressedColor.name(QColor::HexArgb) + ";" + "border-radius:" + QString::number(_cornerRadius) + "px;}"); + } + + _indicator->setStyleSheet("QWidget#indicator{background-color:" + _indicatorColor.name(QColor::HexArgb) + ";" + "border-radius:" + QString::number(_indicatorWidth) + "px;}"); +} + +QString LineEditWidget::text() const { + // Return text + return _editor->text(); +} + +QHBoxLayout* LineEditWidget::mainLayout() const { + // Return main layout + return _mainLayout; +} \ No newline at end of file diff --git a/FinalProject/lineeditwidget.h b/FinalProject/lineeditwidget.h new file mode 100644 index 0000000..ac7a444 --- /dev/null +++ b/FinalProject/lineeditwidget.h @@ -0,0 +1,95 @@ +#pragma once + +#include +#include +#include +#include +#include + +class LineEditWidget : public QWidget { + + Q_OBJECT + +public: + LineEditWidget(QWidget* parent = 0); + ~LineEditWidget(); + +private: + // UI control variables + int _cornerRadius = 8; + + // UI elements + QHBoxLayout* _mainLayout = nullptr; // For layout the user added components and the real editor component + + QWidget* _backgroundWidget = nullptr; + const QColor _defaultColorScheme = QColor(58, 143, 183); + QColor _backgroundColor; + QColor _hoverColor; + QColor _pressedColor; + + QWidget* _editorWidget = nullptr; // Container widget for the real editor component + QHBoxLayout* _editorWidgetLayout = nullptr; + + QLineEdit* _editor = nullptr; + const QFont _defaultFont = QFont("DengXian", 10, QFont::Normal); + + QWidget* _indicator = nullptr; + const int _indicatorWidth = 4; + const int _indicatorSpacing = 4; + QColor _indicatorColor; + QGraphicsOpacityEffect* _indicatorEffect; + + // Editor state + bool _hovered = false; + bool _pressed = false; + bool _enabled = true; + bool _editing = false; + bool _initialized = false; + +private: + // UI Util functions + void initializeUI(); + void generateColor(QColor colorScheme); + + // Interaction Util functions + void startEdit(); + void endEdit(); + +private: + // Interactions + virtual void showEvent(QShowEvent* event) override; + virtual void enterEvent(QEnterEvent* event) override; + virtual void leaveEvent(QEvent* event) override; + virtual void mousePressEvent(QMouseEvent* event) override; + virtual void mouseReleaseEvent(QMouseEvent* event) override; + virtual void focusInEvent(QFocusEvent* event) override; + virtual void focusOutEvent(QFocusEvent* event) override; + virtual void resizeEvent(QResizeEvent* event) override; + virtual bool eventFilter(QObject* obj, QEvent* event) override; + +public: + // Public setter APIs for text + void setText(const QString& text); + void setPlaceholderText(const QString& text); + void setValidator(const QValidator* validator); + + // Publics setter APIs for UI + void setEnabled(bool enabled = true); + void setMargins(QMargins margins); + void setMargins(int left, int top, int right, int bottom); + void setBackgroundColor(QColor color); + void setHoverColor(QColor color); + void setPressedColor(QColor color); + void setIndicatorColor(QColor color); + void setColorScheme(QColor primaryColor); + + // Public getter APIs + QString text() const; + QHBoxLayout* mainLayout() const; + +signals: + // Public signal functions + void onStartEditing(const QString& text); + void onTextChanged(const QString& text); + void onEndEditing(const QString& text); +}; \ No newline at end of file diff --git a/FinalProject/main.cpp b/FinalProject/main.cpp index fddc5d1..12cef37 100644 --- a/FinalProject/main.cpp +++ b/FinalProject/main.cpp @@ -1,13 +1,21 @@ -#include "mainwindow.h" #include +#include +#include + +#include "mainwindow.h" +#include "logger.h" int main(int argc, char *argv[]) { - QApplication a(argc, argv); + QApplication a(argc, argv); // Create main application instance + + QFontDatabase::addApplicationFont(":/fonts/font_awesome_6_regular_free.otf"); // Add font awesome font to application + QFontDatabase::addApplicationFont(":/fonts/font_awesome_6_solid_free.otf"); // Add font awesome font to application + MainWindow w; - w.setWindowFlag(Qt::FramelessWindowHint); - w.setAttribute(Qt::WA_TranslucentBackground); w.setMouseTracking(true); + w.resize(900, 600); w.show(); + return a.exec(); } diff --git a/FinalProject/mainwindow.cpp b/FinalProject/mainwindow.cpp index 8c67927..dee9516 100644 --- a/FinalProject/mainwindow.cpp +++ b/FinalProject/mainwindow.cpp @@ -1,318 +1,97 @@ -#include -#include - #include "mainwindow.h" -#include "sceneviewer.h" #include "logger.h" #define MAX_MOUSE_MOVEMENT 300 MainWindow::MainWindow(QWidget *parent) - : QMainWindow(parent) + : FramelessWindow(20, parent) { - ui.setupUi(this); - 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); + // Create main layout for page and sidebar + _mainLayout = new QHBoxLayout(_windowWidget); + _mainLayout->setContentsMargins(0, 0, 0, 0); + _mainLayout->setSpacing(0); + _windowWidget->setLayout(_mainLayout); + + // Create placeholder widget for pages + _placeHolderWidget = new QWidget(_windowWidget); + _placeHolderWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + _placeHolderWidget->setMouseTracking(true); - // 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(); + // Connect the resize event of the placeholder widget to the resizePages function using event filter + _placeHolderWidget->installEventFilter(this); - // Create window control buttons - _minimizeBtn = new QPushButton(_windowBtnWidget); - _maximizeBtn = new QPushButton(_windowBtnWidget); - _closeBtn = new QPushButton(_windowBtnWidget); + // Create sidebar + _sideBar = new SideBar(_windowWidget); + connect(_sideBar, &SideBar::onPageChanged, this, [=](PageWidget* previousPage, PageWidget* currentPage) { + // Check for input validity + if (previousPage == currentPage) { + return; + } + if (previousPage != nullptr) { + previousPage->offStage(); + } + if (currentPage != nullptr) { + currentPage->onStage(); + } + }); + _sideBar->setMouseTracking(true); + + // Add sidebar and placeholder widget to main layout + _mainLayout->addWidget(_sideBar); + _mainLayout->addWidget(_placeHolderWidget); - _minimizeBtn->setFixedSize(12, 12); - _maximizeBtn->setFixedSize(12, 12); - _closeBtn->setFixedSize(12, 12); + // Create editor page and connect to side bar + _editorPage = new EditorPage(_placeHolderWidget); + _editorPage->setMouseTracking(true); + _sideBar->addPage(_editorPage); - _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); + // Create about page and connect to side bar + _aboutPage = new AboutPage(_placeHolderWidget); + _aboutPage->setMouseTracking(true); + _sideBar->addPage(_aboutPage); } 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) { +void MainWindow::resizePages(QResizeEvent* event) { + // Check for input validity + if (event == nullptr) { 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; - } + + // Get the size of the placeholder widget + QSize size = event->size(); + + // Resize the editor page + _editorPage->resize(size); + _aboutPage->resize(size); } -void MainWindow::mousePressEvent(QMouseEvent* event) { - if (event->button() == Qt::LeftButton) { - _mousePressed = true; - _lastMousePosition = event->globalPos().toPointF(); - } +void MainWindow::showEvent(QShowEvent* event) { + // Call parent show event + FramelessWindow::showEvent(event); + + // Resize all the pages based on the placeholder widget + _editorPage->resize(_placeHolderWidget->size()); + _aboutPage->resize(_placeHolderWidget->size()); } -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(); +bool MainWindow::eventFilter(QObject* object, QEvent* event) { + // Check for input validity + if (object == nullptr || event == nullptr) { + return false; } - 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; + // Check if the object is the placeholder widget + if (object == _placeHolderWidget) { + // Check if the event is a resize event + if (event->type() == QEvent::Resize) { + // Resize all the pages + resizePages(static_cast(event)); } - _lastMousePosition = event->globalPos().toPointF(); } -} + + // Call parent event filter + return FramelessWindow::eventFilter(object, event); +} \ No newline at end of file diff --git a/FinalProject/mainwindow.h b/FinalProject/mainwindow.h index 8376819..d6f63c0 100644 --- a/FinalProject/mainwindow.h +++ b/FinalProject/mainwindow.h @@ -1,12 +1,13 @@ #pragma once -#include -#include -#include -#include -#include "ui_mainwindow.h" +#include -class MainWindow : public QMainWindow +#include "framelesswindow.h" +#include "sidebar.h" +#include "editorpage.h" +#include "aboutpage.h" + +class MainWindow : public FramelessWindow { Q_OBJECT @@ -15,54 +16,21 @@ public: ~MainWindow(); private: - Ui::MainWindowClass ui; + // Main ui layout + QHBoxLayout* _mainLayout = nullptr; + + // Side bar and corresponding pages + SideBar* _sideBar = nullptr; + EditorPage* _editorPage = nullptr; + AboutPage* _aboutPage = nullptr; + + // Place holder widget for resizing pages + QWidget* _placeHolderWidget = nullptr; private: - // UI control variables - const int _cornerRadius = 20; - const QColor _backgroundColor = QColor(251, 251, 251); - const QColor _borderColor = QColor(104, 104, 104); + void resizePages(QResizeEvent* event); - // Window initialize - void initializeWindowUI(); +private: 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; + virtual bool eventFilter(QObject* object, QEvent* event) override; }; diff --git a/FinalProject/mainwindow.qrc b/FinalProject/mainwindow.qrc index 89c5a99..0a9d314 100644 --- a/FinalProject/mainwindow.qrc +++ b/FinalProject/mainwindow.qrc @@ -3,4 +3,8 @@ fragmentshader.fs vertexshader.vs + + font_awesome_6_regular_free.otf + font_awesome_6_solid_free.otf + diff --git a/FinalProject/mainwindow.ui b/FinalProject/mainwindow.ui deleted file mode 100644 index 444ab27..0000000 --- a/FinalProject/mainwindow.ui +++ /dev/null @@ -1,23 +0,0 @@ - - - MainWindowClass - - - - 0 - 0 - 600 - 400 - - - - MainWindow - - - - - - - - - diff --git a/FinalProject/model.cpp b/FinalProject/model.cpp index e1db286..f8e6296 100644 --- a/FinalProject/model.cpp +++ b/FinalProject/model.cpp @@ -13,7 +13,13 @@ Model::~Model() { // TODO: Maybe delete all meshes? } -// file path is ...\\...\\.obj, and processnode & processmesh have been called here +Model::Model(std::vector&& meshes, std::vector&& textures, std::string directory) { + _meshes = std::move(meshes); + _texturesLoaded = std::move(textures); + _directory = directory; + _status = LOADED; +} + void Model::loadModel(std::string path) { Logger::info("Loading model from path: " + path); Assimp::Importer importer; @@ -34,7 +40,7 @@ void Model::loadModel(std::string path) { processNode(scene->mRootNode, scene); _status = LOADED; Logger::info("Model loaded"); - // ½ö¼ì²éÒ»´Î¼´¿É + // 仅检查一次å³å¯ //check_boundary(); } @@ -67,7 +73,7 @@ Mesh Model::processMesh(aiMesh* mesh, const aiScene* scene) { glm::vec3 vertexBitangent = glm::vec3(0.0f); // Process vertex positions - //ʹÓÃÑ­»·±ÜÃâ´úÂëÖØ¸´£¬Èç¹û¿ÉÐеϰ£¬¿ÉÒÔÔÚ´ËÑ­»·ÖÐÈ·¶¨·¨ÏòÁ¿µÈÐÅÏ¢ + //使用循环é¿å…代ç é‡å¤ï¼Œå¦‚æžœå¯è¡Œçš„è¯ï¼Œå¯ä»¥åœ¨æ­¤å¾ªçŽ¯ä¸­ç¡®å®šæ³•å‘é‡ç­‰ä¿¡æ¯ for (int j = 0; j < 3; j++) { vertexPosition[j] = mesh->mVertices[i][j]; _left_down_back[j] = _left_down_back[j] < vertexPosition[j] ? _left_down_back[j] : vertexPosition[j]; @@ -200,5 +206,26 @@ void Model::check_boundary() { } } } +} -} \ No newline at end of file +Model* Model::copyToCurrentContext() const { + // Reload all textures + std::vector newTextures; + for (unsigned int i = 0; i < _texturesLoaded.size(); i++) { + // Load texture + Texture newTexture = Texture(_texturesLoaded[i].type(), _directory + '/' + _texturesLoaded[i].path()); + newTextures.push_back(newTexture); + } + + // Copy all meshes + std::vector newMeshes; + for (unsigned int i = 0; i < _meshes.size(); i++) { + // Copy mesh + Mesh newMesh = Mesh(_meshes[i].vertices(), _meshes[i].indices(), newTextures); + newMeshes.push_back(newMesh); + } + + // Create new model + Model* newModel = new Model(std::move(newMeshes), std::move(newTextures), _directory); + return newModel; +} diff --git a/FinalProject/model.h b/FinalProject/model.h index 7f3df3a..57bbbff 100644 --- a/FinalProject/model.h +++ b/FinalProject/model.h @@ -28,8 +28,16 @@ public: Model(std::string path); ~Model(); +private: + Model(std::vector&& meshes, std::vector&& textures, std::string directory); + public: inline MODELSTATUS status() const { return _status; } + inline int meshCount() const { return _meshes.size(); } + inline int textureCount() const { return _texturesLoaded.size(); } + inline std::string directory() const { return _directory; } + + Model* copyToCurrentContext() const; // maybe we can check if boundary has not been set yet diff --git a/FinalProject/modelselectable.cpp b/FinalProject/modelselectable.cpp index 6f70f09..811585f 100644 --- a/FinalProject/modelselectable.cpp +++ b/FinalProject/modelselectable.cpp @@ -1 +1,83 @@ -#pragma once +#include + +#include "modelselectable.h" +#include "sceneviewer.h" + +ModelSelectable::ModelSelectable(Model* model, QWidget* parent) : + QWidget(parent), _model(model) +{ + // Set size + setFixedHeight(150); + setSizeIncrement(QSizePolicy::Expanding, QSizePolicy::Fixed); + + // Create stretch layout to hold the main button + _stretchLayout = new QHBoxLayout(this); + _stretchLayout->setContentsMargins(0, 0, 0, 0); + _stretchLayout->setSpacing(0); + setLayout(_stretchLayout); + + // Create main button widget + _mainBtn = new PushButton(nullptr, this); + _mainBtn->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + _mainBtn->setMargin(0, 0, 0, 0); + _mainBtn->setRadius(10); + _mainBtn->setBackgroundColor(QColor(58, 143, 183, 15)); + _stretchLayout->addWidget(_mainBtn); + _mainBtn->show(); + + // Create widget to hold the button content + _mainBtnChild = new QWidget(_mainBtn); + _mainBtnChild->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + //_mainBtnChild->setObjectName("mainBtnChild"); + //_mainBtnChild->setStyleSheet("QWidget#mainBtnChild{border: 3px solid " + _borderColor.name(QColor::HexArgb) + ";" + // "border-radius:" + QString::number(_cornerRadius) + "px;}"); + _mainBtn->setChildWidget(_mainBtnChild); + _mainBtnChild->show(); + + // Create layout to hold the button content + _mainLayout = new QHBoxLayout(_mainBtnChild); + _mainLayout->setContentsMargins(16, 12, 16, 12); + _mainLayout->setSpacing(12); + _mainBtnChild->setLayout(_mainLayout); + + // Create model thumbnail widget + _thumbnailContainer = new RoundedCornerWidget(_mainBtnChild); + _thumbnailContainer->setCornerRadius(_cornerRadius); + _thumbnailContainer->setBorderColor(QColor(255, 255, 255, 0)); + _thumbnailContainer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + _mainLayout->addWidget(_thumbnailContainer); + _thumbnailContainer->show(); + + _thumbnailWidget = new ModelThumbnailWidget(_model, _mainBtnChild); + _thumbnailWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + + _thumbnailContainerLayout = new QHBoxLayout(_thumbnailContainer->mainWidget()); + _thumbnailContainerLayout->setContentsMargins(0, 0, 0, 0); + _thumbnailContainerLayout->setSpacing(0); + _thumbnailContainer->mainWidget()->setLayout(_thumbnailContainerLayout); + _thumbnailContainerLayout->addWidget(_thumbnailWidget); + _thumbnailWidget->show(); + + // Create delete button + _deleteButton = new PushButton(nullptr, _mainBtnChild); + _deleteButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + _deleteIcon = new QLabel(_deleteButton); + _deleteIcon->setFont(QFont("Font Awesome 6 Free Regular", 12)); + _deleteIcon->setText("\uf2ed"); + _deleteIcon->setAlignment(Qt::AlignCenter); + _deleteButton->setChildWidget(_deleteIcon); + _deleteButton->setColorScheme(QColor(171, 59, 58)); + _deleteButton->setIndicatorColor(QColor(171, 59, 58, 0)); + _deleteIcon->show(); + _mainLayout->addWidget(_deleteButton); + _deleteButton->show(); + + // Connect + connect(_mainBtn, &PushButton::onClick, this, &ModelSelectable::onSelected); + connect(_deleteButton, &PushButton::onClick, this, &ModelSelectable::onRemoved); +} + +ModelSelectable::~ModelSelectable() +{ + delete _model; +} \ No newline at end of file diff --git a/FinalProject/modelselectable.h b/FinalProject/modelselectable.h index 7fed472..acb94cf 100644 --- a/FinalProject/modelselectable.h +++ b/FinalProject/modelselectable.h @@ -1,6 +1,46 @@ #pragma once -#include "model.h" - #include +#include +#include +#include "model.h" +#include "pushbutton.h" +#include "roundedcornerwidget.h" +#include "modelthumbnailwidget.h" +#include "lineeditwidget.h" + +class ModelSelectable : public QWidget { + + Q_OBJECT + +public: + ModelSelectable(Model* model, QWidget* parent = 0); + ~ModelSelectable(); + +private: + // Data + Model* _model; + + // UI control variables + const QColor _borderColor = QColor(58, 143, 183); + const int _cornerRadius = 10; + + // UI elements + QHBoxLayout* _stretchLayout = nullptr; + PushButton* _mainBtn = nullptr; + + QWidget* _mainBtnChild = nullptr; + QHBoxLayout* _mainLayout = nullptr; + + RoundedCornerWidget* _thumbnailContainer = nullptr; + QHBoxLayout* _thumbnailContainerLayout = nullptr; + ModelThumbnailWidget* _thumbnailWidget = nullptr; + + PushButton* _deleteButton = nullptr; + QLabel* _deleteIcon = nullptr; + +signals: + void onSelected(); + void onRemoved(); +}; diff --git a/FinalProject/modelselector.cpp b/FinalProject/modelselector.cpp index 6f70f09..c7c37c6 100644 --- a/FinalProject/modelselector.cpp +++ b/FinalProject/modelselector.cpp @@ -1 +1,83 @@ -#pragma once +#include + +#include "modelselector.h" + +ModelSelector::ModelSelector(QWidget* parent) : + QWidget(parent) +{ + // Set maximum width + setMaximumWidth(240); + + // Set up the main layout + _stretchLayout = new QVBoxLayout(this); + _stretchLayout->setContentsMargins(0, 0, 0, 0); + _stretchLayout->setSpacing(8); + setLayout(_stretchLayout); + + // Create and add the 'add new' button + _addNewObjectBtn = new PushButton(nullptr, this); + _addNewObjectBtn->setChildWidget(new QLabel("Add New", _addNewObjectBtn)); + _addNewObjectBtn->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + _addNewObjectBtn->setMargin(32, 24, 32, 24); + _addNewObjectBtn->setRadius(10); + _addNewObjectBtn->setBackgroundColor(QColor(58, 143, 183, 20)); + _stretchLayout->addWidget(_addNewObjectBtn); + _addNewObjectBtn->show(); + + // Create the selection list widget + _objectList = new ScrollListWidget(this); + _stretchLayout->addWidget(_objectList); + _objectList->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + _objectList->show(); + + // Connect + connect(_addNewObjectBtn, &PushButton::onClick, this, &ModelSelector::addNewObject); +} + +ModelSelector::~ModelSelector() {} + +void ModelSelector::addNewObject() { + QString path = getNewObjectPath(); + if (path.isEmpty()) { + return; + } + + Model* model = loadObject(path); + ModelSelectable* newSelectable = new ModelSelectable(model, this); + + _objectSelectables.push_back(newSelectable); + _objectList->addWidget(newSelectable); + + connect(newSelectable, &ModelSelectable::onSelected, this, [=]() { + emit onObjectSelected(model); + }); + connect(newSelectable, &ModelSelectable::onRemoved, this, [=]() { + removeObject(newSelectable); + }); +} + +QString ModelSelector::getNewObjectPath() { + QString path = QFileDialog::getOpenFileName(this, "Select Model", "", "Model Files (*.obj)"); + return path; +} + +Model* ModelSelector::loadObject(const QString& path) { + Model* model = new Model(path.toStdString()); + return model; +} + +void ModelSelector::removeObject(ModelSelectable* selectable) { + // Check if selectable is still in list + if (!_objectSelectables.contains(selectable)) { + return; + } + + // Remove the selectable from the list + _objectSelectables.removeAll(selectable); + + // Remove the selectable from the list widget + _objectList->removeWidget(selectable); + + // Delete the selectable + delete selectable; +} diff --git a/FinalProject/modelselector.h b/FinalProject/modelselector.h index 6f70f09..0391f8e 100644 --- a/FinalProject/modelselector.h +++ b/FinalProject/modelselector.h @@ -1 +1,40 @@ #pragma once + +#include +#include +#include + +#include "scrolllistwidget.h" +#include "pushbutton.h" +#include "modelselectable.h" +#include "model.h" + +class ModelSelector : public QWidget +{ + Q_OBJECT + +public: + ModelSelector(QWidget* parent = 0); + ~ModelSelector(); + +private: + // UI elements + QVBoxLayout* _stretchLayout = nullptr; + PushButton* _addNewObjectBtn = nullptr; + ScrollListWidget* _objectList = nullptr; + + // Object selection storage + QList _objectSelectables; + +private: + // Slot functions + void addNewObject(); + + // Util functions + QString getNewObjectPath(); + Model* loadObject(const QString& path); + void removeObject(ModelSelectable* selectable); + +signals: + void onObjectSelected(Model* selectedModel); +}; diff --git a/FinalProject/modelsetter.h b/FinalProject/modelsetter.h index 6f70f09..71fd808 100644 --- a/FinalProject/modelsetter.h +++ b/FinalProject/modelsetter.h @@ -1 +1,3 @@ #pragma once + +#include diff --git a/FinalProject/modelthumbnailwidget.cpp b/FinalProject/modelthumbnailwidget.cpp new file mode 100644 index 0000000..b6e1fcf --- /dev/null +++ b/FinalProject/modelthumbnailwidget.cpp @@ -0,0 +1,75 @@ +#include +#include + +#include "modelthumbnailwidget.h" + +ModelThumbnailWidget::ModelThumbnailWidget(Model* model, QWidget* parent) : + QOpenGLWidget(parent), _model(model) +{ + // OpenGL initialize + QSurfaceFormat format; + format.setProfile(QSurfaceFormat::CoreProfile); + format.setVersion(4, 3); + setFormat(format); + + // Create a folder + QDir dir("./temp/shaders"); + if (!dir.exists()) { + dir.mkpath("."); + } + + // Copy the shaders to the folder + if (QFile::exists("./temp/shaders/vertexshader.vs")) { + QFile::remove("./temp/shaders/vertexshader.vs"); + } + QFile::copy(":/shaders/vertexshader.vs", "./temp/shaders/vertexshader.vs"); + QFile::setPermissions("./temp/shaders/vertexshader.vs", QFileDevice::ReadOwner | QFileDevice::WriteOwner); + if (QFile::exists("./temp/shaders/fragmentshader.fs")) { + QFile::remove("./temp/shaders/fragmentshader.fs"); + } + QFile::copy(":/shaders/fragmentshader.fs", "./temp/shaders/fragmentshader.fs"); + QFile::setPermissions("./temp/shaders/fragmentshader.fs", QFile::ReadOwner | QFile::WriteOwner); +} + +ModelThumbnailWidget::~ModelThumbnailWidget() { +} + +void ModelThumbnailWidget::initializeGL() { + initializeOpenGLFunctions(); + + glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glEnable(GL_DEPTH_TEST); + + _shaderProgram.ensureInitialized(); + + VertexShader vertexShader("./temp/shaders/vertexshader.vs"); + FragmentShader fragmentShader("./temp/shaders/fragmentshader.fs"); + _shaderProgram.attachShader(vertexShader); + _shaderProgram.attachShader(fragmentShader); + vertexShader.dispose(); + fragmentShader.dispose(); + + _object.setModel(_model->copyToCurrentContext()); + _camera.setPosition(glm::vec3(0.0f, 0.0f, 7.0f)); +} + +void ModelThumbnailWidget::paintGL() { + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + _shaderProgram.bind(); + + glm::mat4 view = _camera.viewMatrix(); + glm::mat4 projection = glm::perspective(glm::radians(_camera.zoomVal()), (float)width() / (float)height(), 0.1f, 100.0f); + _shaderProgram.setUniform("view", view); + _shaderProgram.setUniform("projection", projection); + + _object.render(_shaderProgram); + + _shaderProgram.unbind(); +} + +void ModelThumbnailWidget::resizeGL(int w, int h) { + glViewport(0, 0, w, h); +} \ No newline at end of file diff --git a/FinalProject/modelthumbnailwidget.h b/FinalProject/modelthumbnailwidget.h new file mode 100644 index 0000000..05377ed --- /dev/null +++ b/FinalProject/modelthumbnailwidget.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include + +#include "model.h" +#include "camera.h" +#include "shader.h" +#include "renderable.h" + +class ModelThumbnailWidget : public QOpenGLWidget, protected QOpenGLFunctions +{ + Q_OBJECT + +public: + ModelThumbnailWidget(Model* model, QWidget* parent = 0); + ~ModelThumbnailWidget(); + +private: + Model* _model = nullptr; + Renderable _object = Renderable::empty(); + ShaderProgram _shaderProgram = ShaderProgram::empty(); + Camera _camera; + +protected: + virtual void initializeGL() override; + virtual void paintGL() override; + virtual void resizeGL(int w, int h) override; +}; diff --git a/FinalProject/pagewidget.cpp b/FinalProject/pagewidget.cpp new file mode 100644 index 0000000..ec5d3a3 --- /dev/null +++ b/FinalProject/pagewidget.cpp @@ -0,0 +1,72 @@ +#include +#include + +#include "pagewidget.h" + +PageWidget::PageWidget(QWidget* parent) : QWidget(parent) { + // Construct and set main layout + _stretchLayout = new QVBoxLayout(this); + _stretchLayout->setContentsMargins(0, 0, 0, 0); + _stretchLayout->setSpacing(0); + setLayout(_stretchLayout); + + // Construct content widget + _contentWidget = new QWidget(this); + _stretchLayout->addWidget(_contentWidget); + _contentWidget->show(); + + // Add opacity effect to real content + _pageOpacityEffect = new QGraphicsOpacityEffect(_contentWidget); + _pageOpacityEffect->setOpacity(0); + _contentWidget->setGraphicsEffect(_pageOpacityEffect); + + // Move offstage + move(_originPagePosition + QPoint(0, 150)); + hide(); +} + +PageWidget::~PageWidget() {} + +void PageWidget::onStage() { + // Move up and fade in + QParallelAnimationGroup* onStageAnimation = new QParallelAnimationGroup(this); + QPropertyAnimation* moveAnimation = new QPropertyAnimation(this, "pos"); + QPropertyAnimation* fadeInAnimation = new QPropertyAnimation(_pageOpacityEffect, "opacity"); + moveAnimation->setDuration(600); + moveAnimation->setEasingCurve(QEasingCurve::OutExpo); + moveAnimation->setStartValue(pos()); + moveAnimation->setEndValue(_originPagePosition); + fadeInAnimation->setDuration(500); + fadeInAnimation->setEasingCurve(QEasingCurve::InQuad); + fadeInAnimation->setStartValue(_pageOpacityEffect->opacity()); + fadeInAnimation->setEndValue(0.999); + onStageAnimation->addAnimation(moveAnimation); + onStageAnimation->addAnimation(fadeInAnimation); + onStageAnimation->start(QAbstractAnimation::DeleteWhenStopped); + + // Show page + show(); +} + +void PageWidget::offStage() { + // Move down and fade out + QParallelAnimationGroup* offStageAnimation = new QParallelAnimationGroup(this); + //QPropertyAnimation* moveAnimation = new QPropertyAnimation(_contentWidget, "pos"); + QPropertyAnimation* fadeOutAnimation = new QPropertyAnimation(_pageOpacityEffect, "opacity"); + //moveAnimation->setDuration(300); + //moveAnimation->setEasingCurve(QEasingCurve::OutCubic); + //moveAnimation->setStartValue(_contentWidget->pos()); + //moveAnimation->setEndValue(_originPagePosition - QPoint(0, 100)); + fadeOutAnimation->setDuration(200); + fadeOutAnimation->setStartValue(_pageOpacityEffect->opacity()); + fadeOutAnimation->setEndValue(0); + //offStageAnimation->addAnimation(moveAnimation); + offStageAnimation->addAnimation(fadeOutAnimation); + offStageAnimation->start(QAbstractAnimation::DeleteWhenStopped); + + // Connect animation finished signal to hide page + connect(offStageAnimation, &QParallelAnimationGroup::finished, [=]() { + move(_originPagePosition + QPoint(0, 150)); + hide(); + }); +} \ No newline at end of file diff --git a/FinalProject/pagewidget.h b/FinalProject/pagewidget.h new file mode 100644 index 0000000..1de30fb --- /dev/null +++ b/FinalProject/pagewidget.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include + +#include "pushbutton.h" + +class PageWidget : public QWidget +{ + + Q_OBJECT + +public: + PageWidget(QWidget* parent = 0); + ~PageWidget(); + +protected: + // Page layout and content widget (which holds the actual page elements) + QVBoxLayout* _stretchLayout = nullptr; + QWidget* _contentWidget = nullptr; + + // Button widgets for side bar + PushButton* _iconButton = nullptr; + PushButton* _textButton = nullptr; + + // Opacity effects + QGraphicsOpacityEffect* _pageOpacityEffect = nullptr; + + // Page position memory + QPoint _originPagePosition = QPoint(0, 0); + +public: + virtual PushButton* getPageIconButton(QWidget* context) = 0; // provide a push button with only an icon + virtual PushButton* getPageTextButton(QWidget* context) = 0; // provide a push button with an icon and a description text + +public: + void onStage(); // provide an on stage animation when the page is selected + void offStage(); // provide an off stage animation when the page is deselected +}; \ No newline at end of file diff --git a/FinalProject/pushbutton.cpp b/FinalProject/pushbutton.cpp new file mode 100644 index 0000000..0470b86 --- /dev/null +++ b/FinalProject/pushbutton.cpp @@ -0,0 +1,811 @@ +#include +#include +#include +#include + +#include "pushbutton.h" + +PushButton::PushButton(QWidget* child, QWidget* parent) + : QWidget(parent), _childWidget(child) +{ + initializeUI(); +} + +PushButton::~PushButton() +{ +} + +void PushButton::initializeUI() { + generateColor(_defaultColorScheme); + + // Add margin for the child widget + _stretchLayout = new QHBoxLayout(this); + _stretchLayout->setContentsMargins(_contentMargin); + _stretchLayout->setSpacing(0); + _stretchLayout->setAlignment(Qt::AlignCenter); + setLayout(_stretchLayout); + if (_childWidget) { + // prevent adding a null widget + _stretchLayout->addWidget(_childWidget); + _childWidget->show(); + _childWidgetOriginalGeometry = _childWidget->geometry(); + } + + // Initialize background widget + _backgroundWidget = new QWidget(this); + _backgroundWidget->resize(size()); + _backgroundWidget->setObjectName("backgroundWidget"); + _backgroundWidget->setStyleSheet("QWidget#backgroundWidget{background-color:" + _backgroundColor.name(QColor::HexArgb) + ";border-radius:" + QString::number(_radius) + "px;}"); + _backgroundWidget->lower(); + _backgroundWidget->show(); + + // Initialize indicator + _indicator = new QWidget(this); + switch (_indicatorPosition) { + case LUI_BTN_POS_LEFT: + _indicator->resize(_indicatorWidth, _activatedLengthRatio * height()); + _indicator->move(_indicatorSpacing, (0.5 - _activatedLengthRatio / 2) * height()); + break; + case LUI_BTN_POS_RIGHT: + _indicator->resize(_indicatorWidth, _activatedLengthRatio * height()); + _indicator->move(width() - _indicatorWidth - _indicatorSpacing, (0.5 - _activatedLengthRatio / 2) * height()); + break; + case LUI_BTN_POS_TOP: + _indicator->resize(_activatedLengthRatio * width(), _indicatorWidth); + _indicator->move((0.5 - _activatedLengthRatio / 2) * width(), _indicatorSpacing); + break; + case LUI_BTN_POS_BOTTOM: + _indicator->resize(_activatedLengthRatio * width(), _indicatorWidth); + _indicator->move((0.5 - _activatedLengthRatio / 2) * width(), height() - _indicatorWidth - _indicatorSpacing); + break; + } + _indicator->setObjectName("indicator"); + _indicator->setStyleSheet("QWidget#indicator{background-color:" + _indicatorColor.name(QColor::HexArgb) + ";" + "border-radius:" + QString::number((float)_indicatorWidth / 2) + "px;}"); + _indicatorEffect = new QGraphicsOpacityEffect(_indicator); + _indicatorEffect->setOpacity(0); + _indicator->setGraphicsEffect(_indicatorEffect); + _indicator->show(); + + // Enable mouse tracking + setMouseTracking(true); +} + +void PushButton::generateColor(QColor colorScheme) { + _backgroundColor = colorScheme.lighter(120); + _backgroundColor.setAlpha(0); + _hoverColor = colorScheme.lighter(120); + _hoverColor.setAlpha(40); + _pressedColor = colorScheme.lighter(120); + _pressedColor.setAlpha(50); + _selectedColor = colorScheme.lighter(120); + _selectedColor.setAlpha(35); + _indicatorColor = colorScheme; +} + +void PushButton::enterEvent(QEnterEvent* event) { + setCursor(Qt::PointingHandCursor); + + _backgroundWidget->setStyleSheet("QWidget#backgroundWidget{background-color:" + _hoverColor.name(QColor::HexArgb) + ";border-radius:" + QString::number(_radius) + "px;}"); + + QParallelAnimationGroup* indicatorEnterAnimation = new QParallelAnimationGroup(this); + QPropertyAnimation* indicatorGrowLength = new QPropertyAnimation(_indicator, "geometry", this); + QPropertyAnimation* indicatorFadeIn = new QPropertyAnimation(_indicatorEffect, "opacity", this); + indicatorGrowLength->setDuration(150); + indicatorGrowLength->setEasingCurve(QEasingCurve::OutQuad); + indicatorFadeIn->setDuration(100); + indicatorFadeIn->setEasingCurve(QEasingCurve::OutQuad); + indicatorGrowLength->setStartValue(_indicator->geometry()); + switch (_indicatorPosition) { + case LUI_BTN_POS_LEFT: + indicatorGrowLength->setEndValue(QRect( + _indicatorSpacing, + (0.5 - _hoveredLengthRatio / 2) * height(), + _indicatorWidth, + _hoveredLengthRatio * height() + )); + break; + case LUI_BTN_POS_RIGHT: + indicatorGrowLength->setEndValue(QRect( + width() - _indicatorWidth - _indicatorSpacing, + (0.5 - _hoveredLengthRatio / 2) * height(), + _indicatorWidth, + _hoveredLengthRatio * height() + )); + break; + case LUI_BTN_POS_TOP: + indicatorGrowLength->setEndValue(QRect( + (0.5 - _hoveredLengthRatio / 2) * width(), + _indicatorSpacing, + _hoveredLengthRatio * width(), + _indicatorWidth + )); + break; + case LUI_BTN_POS_BOTTOM: + indicatorGrowLength->setEndValue(QRect( + (0.5 - _hoveredLengthRatio / 2) * width(), + height() - _indicatorWidth - _indicatorSpacing, + _hoveredLengthRatio * width(), + _indicatorWidth + )); + break; + } + indicatorFadeIn->setStartValue(_indicatorEffect->opacity()); + indicatorFadeIn->setEndValue(0.999); + indicatorEnterAnimation->addAnimation(indicatorGrowLength); + indicatorEnterAnimation->addAnimation(indicatorFadeIn); + indicatorEnterAnimation->start(QAbstractAnimation::DeleteWhenStopped); + + _hovered = true; + emit onHover(); +} + +void PushButton::leaveEvent(QEvent* event) { + setCursor(Qt::ArrowCursor); + + if (_selected) { + _backgroundWidget->setStyleSheet("QWidget#backgroundWidget{background-color:" + _selectedColor.name(QColor::HexArgb) + ";border-radius:" + QString::number(_radius) + "px;}"); + } + else { + _backgroundWidget->setStyleSheet("QWidget#backgroundWidget{background-color:" + _backgroundColor.name(QColor::HexArgb) + ";border-radius:" + QString::number(_radius) + "px;}"); + } + + QParallelAnimationGroup* indicatorLeaveAnimation = new QParallelAnimationGroup(this); + QPropertyAnimation* indicatorShrinkLength = new QPropertyAnimation(_indicator, "geometry", this); + QPropertyAnimation* indicatorFadeOut = new QPropertyAnimation(_indicatorEffect, "opacity", this); + indicatorShrinkLength->setDuration(150); + indicatorShrinkLength->setEasingCurve(QEasingCurve::OutQuad); + indicatorFadeOut->setDuration(100); + indicatorFadeOut->setEasingCurve(QEasingCurve::OutQuad); + indicatorShrinkLength->setStartValue(_indicator->geometry()); + switch (_indicatorPosition) { + case LUI_BTN_POS_LEFT: + indicatorShrinkLength->setEndValue(QRect( + _indicatorSpacing, + (0.5 - _activatedLengthRatio / 2) * height(), + _indicatorWidth, + _activatedLengthRatio * height() + )); + break; + case LUI_BTN_POS_RIGHT: + indicatorShrinkLength->setEndValue(QRect( + width() - _indicatorWidth - _indicatorSpacing, + (0.5 - _activatedLengthRatio / 2) * height(), + _indicatorWidth, + _activatedLengthRatio * height() + )); + break; + case LUI_BTN_POS_TOP: + indicatorShrinkLength->setEndValue(QRect( + (0.5 - _activatedLengthRatio / 2) * width(), + _indicatorSpacing, + _activatedLengthRatio * width(), + _indicatorWidth + )); + break; + case LUI_BTN_POS_BOTTOM: + indicatorShrinkLength->setEndValue(QRect( + (0.5 - _activatedLengthRatio / 2) * width(), + height() - _indicatorWidth - _indicatorSpacing, + _activatedLengthRatio * width(), + _indicatorWidth + )); + break; + } + indicatorFadeOut->setStartValue(_indicatorEffect->opacity()); + indicatorFadeOut->setEndValue(_selected ? 0.999 : 0); + indicatorLeaveAnimation->addAnimation(indicatorShrinkLength); + indicatorLeaveAnimation->addAnimation(indicatorFadeOut); + indicatorLeaveAnimation->start(QAbstractAnimation::DeleteWhenStopped); + + _pressed = false; + _hovered = false; +} + +void PushButton::mousePressEvent(QMouseEvent* event) { + _backgroundWidget->setStyleSheet("QWidget#backgroundWidget{background-color:" + _pressedColor.name(QColor::HexArgb) + ";border-radius:" + QString::number(_radius) + "px;}"); + + QPropertyAnimation* indicatorShrinkLength = new QPropertyAnimation(_indicator, "geometry", this); + indicatorShrinkLength->setDuration(100); + indicatorShrinkLength->setEasingCurve(QEasingCurve::OutQuad); + indicatorShrinkLength->setStartValue(_indicator->geometry()); + switch (_indicatorPosition) { + case LUI_BTN_POS_LEFT: + indicatorShrinkLength->setEndValue(QRect( + _indicatorSpacing, + (0.5 - _pressedLengthRatio / 2) * height(), + _indicatorWidth, + _pressedLengthRatio * height() + )); + break; + case LUI_BTN_POS_RIGHT: + indicatorShrinkLength->setEndValue(QRect( + width() - _indicatorWidth - _indicatorSpacing, + (0.5 - _pressedLengthRatio / 2) * height(), + _indicatorWidth, + _pressedLengthRatio * height() + )); + break; + case LUI_BTN_POS_TOP: + indicatorShrinkLength->setEndValue(QRect( + (0.5 - _pressedLengthRatio / 2) * width(), + _indicatorSpacing, + _pressedLengthRatio * width(), + _indicatorWidth + )); + break; + case LUI_BTN_POS_BOTTOM: + indicatorShrinkLength->setEndValue(QRect( + (0.5 - _pressedLengthRatio / 2) * width(), + height() - _indicatorWidth - _indicatorSpacing, + _pressedLengthRatio * width(), + _indicatorWidth + )); + break; + } + indicatorShrinkLength->start(QAbstractAnimation::DeleteWhenStopped); + + _pressed = true; + + emit onPressed(); +} + +void PushButton::mouseReleaseEvent(QMouseEvent* event) { + if (!_pressed) { + return; + } + + _backgroundWidget->setStyleSheet("QWidget#backgroundWidget{background-color:" + _hoverColor.name(QColor::HexArgb) + ";border-radius:" + QString::number(_radius) + "px;}"); + + QPropertyAnimation* indicatorGrowLength = new QPropertyAnimation(_indicator, "geometry", this); + indicatorGrowLength->setDuration(100); + indicatorGrowLength->setEasingCurve(QEasingCurve::OutQuad); + indicatorGrowLength->setStartValue(_indicator->geometry()); + switch (_indicatorPosition) { + case LUI_BTN_POS_LEFT: + indicatorGrowLength->setEndValue(QRect( + _indicatorSpacing, + (0.5 - _hoveredLengthRatio / 2) * height(), + _indicatorWidth, + _hoveredLengthRatio * height() + )); + break; + case LUI_BTN_POS_RIGHT: + indicatorGrowLength->setEndValue(QRect( + width() - _indicatorWidth - _indicatorSpacing, + (0.5 - _hoveredLengthRatio / 2) * height(), + _indicatorWidth, + _hoveredLengthRatio * height() + )); + break; + case LUI_BTN_POS_TOP: + indicatorGrowLength->setEndValue(QRect( + (0.5 - _hoveredLengthRatio / 2) * width(), + _indicatorSpacing, + _hoveredLengthRatio * width(), + _indicatorWidth + )); + break; + case LUI_BTN_POS_BOTTOM: + indicatorGrowLength->setEndValue(QRect( + (0.5 - _hoveredLengthRatio / 2) * width(), + height() - _indicatorWidth - _indicatorSpacing, + _hoveredLengthRatio * width(), + _indicatorWidth + )); + break; + } + indicatorGrowLength->start(QAbstractAnimation::DeleteWhenStopped); + + if (_pressed) { + // prevent double trigger if mouse is first left and then released + emit onClick(); + } + + _pressed = false; +} + +void PushButton::resizeEvent(QResizeEvent* event) { + _backgroundWidget->setGeometry(0, 0, width(), height()); + switch (_indicatorPosition) { + case LUI_BTN_POS_LEFT: + if (_pressed) { + _indicator->setGeometry(QRect( + _indicatorSpacing, + (0.5 - _pressedLengthRatio / 2) * height(), + _indicatorWidth, + _pressedLengthRatio * height() + )); + } + else if (_hovered) { + _indicator->setGeometry(QRect( + _indicatorSpacing, + (0.5 - _hoveredLengthRatio / 2) * height(), + _indicatorWidth, + _hoveredLengthRatio * height() + )); + } + else { + _indicator->setGeometry(QRect( + _indicatorSpacing, + (0.5 - _activatedLengthRatio / 2) * height(), + _indicatorWidth, + _activatedLengthRatio * height() + )); + } + break; + case LUI_BTN_POS_RIGHT: + if (_pressed) { + _indicator->setGeometry(QRect( + width() - _indicatorWidth - _indicatorSpacing, + (0.5 - _pressedLengthRatio / 2) * height(), + _indicatorWidth, + _pressedLengthRatio * height() + )); + } + else if (_hovered) { + _indicator->setGeometry(QRect( + width() - _indicatorWidth - _indicatorSpacing, + (0.5 - _hoveredLengthRatio / 2) * height(), + _indicatorWidth, + _hoveredLengthRatio * height() + )); + } + else { + _indicator->setGeometry(QRect( + width() - _indicatorWidth - _indicatorSpacing, + (0.5 - _activatedLengthRatio / 2) * height(), + _indicatorWidth, + _activatedLengthRatio * height() + )); + } + break; + case LUI_BTN_POS_TOP: + if (_pressed) { + _indicator->setGeometry(QRect( + (0.5 - _pressedLengthRatio / 2) * width(), + _indicatorSpacing, + _pressedLengthRatio * width(), + _indicatorWidth + )); + } + else if (_hovered) { + _indicator->setGeometry(QRect( + (0.5 - _hoveredLengthRatio / 2) * width(), + _indicatorSpacing, + _hoveredLengthRatio * width(), + _indicatorWidth + )); + } + else { + _indicator->setGeometry(QRect( + (0.5 - _activatedLengthRatio / 2) * width(), + _indicatorSpacing, + _activatedLengthRatio * width(), + _indicatorWidth + )); + } + break; + case LUI_BTN_POS_BOTTOM: + if (_pressed) { + _indicator->setGeometry(QRect( + (0.5 - _pressedLengthRatio / 2) * width(), + height() - _indicatorWidth - _indicatorSpacing, + _pressedLengthRatio * width(), + _indicatorWidth + )); + } + else if (_hovered) { + _indicator->setGeometry(QRect( + (0.5 - _hoveredLengthRatio / 2) * width(), + height() - _indicatorWidth - _indicatorSpacing, + _hoveredLengthRatio * width(), + _indicatorWidth + )); + } + else { + _indicator->setGeometry(QRect( + (0.5 - _activatedLengthRatio / 2) * width(), + height() - _indicatorWidth - _indicatorSpacing, + _activatedLengthRatio * width(), + _indicatorWidth + )); + } + break; + } +} + +void PushButton::select() { + if (_pressed) { + _backgroundWidget->setStyleSheet("QWidget#backgroundWidget{background-color:" + _pressedColor.name(QColor::HexArgb) + ";border-radius:" + QString::number(_radius) + "px;}"); + } + else if (_hovered) { + _backgroundWidget->setStyleSheet("QWidget#backgroundWidget{background-color:" + _hoverColor.name(QColor::HexArgb) + ";border-radius:" + QString::number(_radius) + "px;}"); + } + else { + _backgroundWidget->setStyleSheet("QWidget#backgroundWidget{background-color:" + _selectedColor.name(QColor::HexArgb) + ";border-radius:" + QString::number(_radius) + "px;}"); + } + + // First shrink then length the indicator, also fade in + QSequentialAnimationGroup* indicatorSelectAnimation = new QSequentialAnimationGroup(this); + QParallelAnimationGroup* indicatorShrinkAnimation = new QParallelAnimationGroup(this); + QPropertyAnimation* indicatorFadeIn = new QPropertyAnimation(_indicatorEffect, "opacity", this); + QPropertyAnimation* indicatorShrinkLength = new QPropertyAnimation(_indicator, "geometry", this); + QPropertyAnimation* indicatorGrowLength = new QPropertyAnimation(_indicator, "geometry", this); + indicatorFadeIn->setDuration(100); + indicatorFadeIn->setEasingCurve(QEasingCurve::OutQuad); + indicatorShrinkLength->setDuration(100); + indicatorShrinkLength->setEasingCurve(QEasingCurve::OutQuad); + indicatorGrowLength->setDuration(100); + indicatorGrowLength->setEasingCurve(QEasingCurve::OutQuad); + indicatorFadeIn->setStartValue(_indicatorEffect->opacity()); + indicatorFadeIn->setEndValue(0.999); + indicatorShrinkLength->setStartValue(_indicator->geometry()); + switch (_indicatorPosition) { + case LUI_BTN_POS_LEFT: + indicatorShrinkLength->setEndValue(QRect( + _indicatorSpacing, + (0.5 - _pressedLengthRatio / 2) * height(), + _indicatorWidth, + _pressedLengthRatio * height() + )); + indicatorGrowLength->setStartValue(QRect( + _indicatorSpacing, + (0.5 - _pressedLengthRatio / 2) * height(), + _indicatorWidth, + _pressedLengthRatio * height() + )); + indicatorGrowLength->setEndValue(QRect( + _indicatorSpacing, + (0.5 - _activatedLengthRatio / 2) * height(), + _indicatorWidth, + _activatedLengthRatio * height() + )); + break; + case LUI_BTN_POS_RIGHT: + indicatorShrinkLength->setEndValue(QRect( + width() - _indicatorWidth - _indicatorSpacing, + (0.5 - _pressedLengthRatio / 2) * height(), + _indicatorWidth, + _pressedLengthRatio * height() + )); + indicatorGrowLength->setStartValue(QRect( + width() - _indicatorWidth - _indicatorSpacing, + (0.5 - _pressedLengthRatio / 2) * height(), + _indicatorWidth, + _pressedLengthRatio * height() + )); + indicatorGrowLength->setEndValue(QRect( + width() - _indicatorWidth - _indicatorSpacing, + (0.5 - _activatedLengthRatio / 2) * height(), + _indicatorWidth, + _activatedLengthRatio * height() + )); + break; + case LUI_BTN_POS_TOP: + indicatorShrinkLength->setEndValue(QRect( + (0.5 - _pressedLengthRatio / 2) * width(), + _indicatorSpacing, + _pressedLengthRatio * width(), + _indicatorWidth + )); + indicatorGrowLength->setStartValue(QRect( + (0.5 - _pressedLengthRatio / 2) * width(), + _indicatorSpacing, + _pressedLengthRatio * width(), + _indicatorWidth + )); + indicatorGrowLength->setEndValue(QRect( + (0.5 - _activatedLengthRatio / 2) * width(), + _indicatorSpacing, + _activatedLengthRatio * width(), + _indicatorWidth + )); + break; + case LUI_BTN_POS_BOTTOM: + indicatorShrinkLength->setEndValue(QRect( + (0.5 - _pressedLengthRatio / 2) * width(), + height() - _indicatorWidth - _indicatorSpacing, + _pressedLengthRatio * width(), + _indicatorWidth + )); + indicatorGrowLength->setStartValue(QRect( + (0.5 - _pressedLengthRatio / 2) * width(), + height() - _indicatorWidth - _indicatorSpacing, + _pressedLengthRatio * width(), + _indicatorWidth + )); + indicatorGrowLength->setEndValue(QRect( + (0.5 - _activatedLengthRatio / 2) * width(), + height() - _indicatorWidth - _indicatorSpacing, + _activatedLengthRatio * width(), + _indicatorWidth + )); + break; + } + indicatorShrinkAnimation->addAnimation(indicatorShrinkLength); + indicatorShrinkAnimation->addAnimation(indicatorFadeIn); + indicatorSelectAnimation->addAnimation(indicatorShrinkAnimation); + indicatorSelectAnimation->addAnimation(indicatorGrowLength); + indicatorSelectAnimation->start(QAbstractAnimation::DeleteWhenStopped); + + // Set selected state + _selected = true; +} + +void PushButton::deselect() { + if (!_selected) { + return; + } + + if (_pressed) { + _backgroundWidget->setStyleSheet("QWidget#backgroundWidget{background-color:" + _pressedColor.name(QColor::HexArgb) + ";border-radius:" + QString::number(_radius) + "px;}"); + } + else if (_hovered) { + _backgroundWidget->setStyleSheet("QWidget#backgroundWidget{background-color:" + _hoverColor.name(QColor::HexArgb) + ";border-radius:" + QString::number(_radius) + "px;}"); + } + else { + _backgroundWidget->setStyleSheet("QWidget#backgroundWidget{background-color:" + _backgroundColor.name(QColor::HexArgb) + ";border-radius:" + QString::number(_radius) + "px;}"); + + // Cursor is currently not in the button, shrink and fade out the indicator + QParallelAnimationGroup* indicatorDeselectAnimation = new QParallelAnimationGroup(this); + QPropertyAnimation* indicatorFadeOut = new QPropertyAnimation(_indicatorEffect, "opacity", this); + QPropertyAnimation* indicatorShrinkLength = new QPropertyAnimation(_indicator, "geometry", this); + indicatorFadeOut->setDuration(100); + indicatorFadeOut->setEasingCurve(QEasingCurve::OutQuad); + indicatorShrinkLength->setDuration(100); + indicatorShrinkLength->setEasingCurve(QEasingCurve::OutQuad); + indicatorFadeOut->setStartValue(_indicatorEffect->opacity()); + indicatorFadeOut->setEndValue(0); + indicatorShrinkLength->setStartValue(_indicator->geometry()); + switch (_indicatorPosition) { + case LUI_BTN_POS_LEFT: + indicatorShrinkLength->setEndValue(QRect( + _indicatorSpacing, + (0.5 - _pressedLengthRatio / 2) * height(), + _indicatorWidth, + _pressedLengthRatio * height() + )); + break; + case LUI_BTN_POS_RIGHT: + indicatorShrinkLength->setEndValue(QRect( + width() - _indicatorWidth - _indicatorSpacing, + (0.5 - _pressedLengthRatio / 2) * height(), + _indicatorWidth, + _pressedLengthRatio * height() + )); + break; + case LUI_BTN_POS_TOP: + indicatorShrinkLength->setEndValue(QRect( + (0.5 - _pressedLengthRatio / 2) * width(), + _indicatorSpacing, + _pressedLengthRatio * width(), + _indicatorWidth + )); + break; + case LUI_BTN_POS_BOTTOM: + indicatorShrinkLength->setEndValue(QRect( + (0.5 - _pressedLengthRatio / 2) * width(), + height() - _indicatorWidth - _indicatorSpacing, + _pressedLengthRatio * width(), + _indicatorWidth + )); + break; + } + indicatorDeselectAnimation->addAnimation(indicatorFadeOut); + indicatorDeselectAnimation->addAnimation(indicatorShrinkLength); + indicatorDeselectAnimation->start(QAbstractAnimation::DeleteWhenStopped); + } + + // Set selected state + _selected = false; +} + +void PushButton::setRadius(int radius) { + // get current style sheet + QString styleSheet = _backgroundWidget->styleSheet(); + // remove old border radius + styleSheet.remove(QRegularExpression("border-radius:\\d+px;")); + // add new border radius + styleSheet.append("border-radius: " + QString::number(radius) + "px;"); + // set new style sheet + _backgroundWidget->setStyleSheet(styleSheet); +} + +void PushButton::setBackgroundColor(QColor color) { + _backgroundColor = color; + if (!_selected && !_hovered && !_pressed) { + _backgroundWidget->setStyleSheet("QWidget#backgroundWidget{background-color:" + _backgroundColor.name(QColor::HexArgb) + ";border-radius:" + QString::number(_radius) + "px;}"); + } +} + +void PushButton::setHoverColor(QColor color) { + _hoverColor = color; + if (_hovered && !_pressed) { + _backgroundWidget->setStyleSheet("QWidget#backgroundWidget{background-color:" + _hoverColor.name(QColor::HexArgb) + ";border-radius:" + QString::number(_radius) + "px;}"); + } +} + +void PushButton::setPressedColor(QColor color) { + _pressedColor = color; + if (_pressed) { + _backgroundWidget->setStyleSheet("QWidget#backgroundWidget{background-color:" + _pressedColor.name(QColor::HexArgb) + ";border-radius:" + QString::number(_radius) + "px;}"); + } +} + +void PushButton::setSelectedColor(QColor color) { + _selectedColor = color; + if (_selected && !_pressed && !_hovered) { + _backgroundWidget->setStyleSheet("QWidget#backgroundWidget{background-color:" + _selectedColor.name(QColor::HexArgb) + ";border-radius:" + QString::number(_radius) + "px;}"); + } +} + +void PushButton::setIndicatorColor(QColor color) { + _indicatorColor = color; + _indicator->setStyleSheet("QWidget#indicator{background-color:" + _indicatorColor.name(QColor::HexArgb) + ";" + "border-radius:" + QString::number((float)_indicatorWidth / 2) + "px;}"); +} + +void PushButton::setColorScheme(QColor color) { + generateColor(color); + if (_pressed) { + _backgroundWidget->setStyleSheet("QWidget#backgroundWidget{background-color:" + _pressedColor.name(QColor::HexArgb) + ";border-radius:" + QString::number(_radius) + "px;}"); + } + else if (_hovered) { + _backgroundWidget->setStyleSheet("QWidget#backgroundWidget{background-color:" + _hoverColor.name(QColor::HexArgb) + ";border-radius:" + QString::number(_radius) + "px;}"); + } + else if (_selected) { + _backgroundWidget->setStyleSheet("QWidget#backgroundWidget{background-color:" + _selectedColor.name(QColor::HexArgb) + ";border-radius:" + QString::number(_radius) + "px;}"); + } + else { + _backgroundWidget->setStyleSheet("QWidget#backgroundWidget{background-color:" + _backgroundColor.name(QColor::HexArgb) + ";border-radius:" + QString::number(_radius) + "px;}"); + } + _indicator->setStyleSheet("QWidget#indicator{background-color:" + _indicatorColor.name(QColor::HexArgb) + ";" + "border-radius:" + QString::number((float)_indicatorWidth / 2) + "px;}"); +} + +QHBoxLayout* PushButton::mainLayout() const { + return _stretchLayout; +} + +void PushButton::setAlignment(Qt::Alignment alignment) { + _stretchLayout->setAlignment(alignment); +} + +void PushButton::setMargin(QMargins margin) { + _stretchLayout->setContentsMargins(margin); +} + +void PushButton::setMargin(int left, int top, int right, int bottom) { + _stretchLayout->setContentsMargins(left, top, right, bottom); +} + +void PushButton::setIndicatorPosition(LUI_BTN_INDICATOR_POS pos) { + _indicatorPosition = pos; + switch (pos) { + case LUI_BTN_POS_LEFT: + if (_pressed) { + _indicator->setGeometry( + _indicatorSpacing, + (0.5 - _pressedLengthRatio / 2) * height(), + _indicatorWidth, + _pressedLengthRatio * height() + ); + } + else if (_hovered) { + _indicator->setGeometry( + _indicatorSpacing, + (0.5 - _hoveredLengthRatio / 2) * height(), + _indicatorWidth, + _hoveredLengthRatio * height() + ); + } + else { + _indicator->setGeometry( + _indicatorSpacing, + (0.5 - _activatedLengthRatio / 2) * height(), + _indicatorWidth, + _activatedLengthRatio * height() + ); + } + break; + case LUI_BTN_POS_RIGHT: + if (_pressed) { + _indicator->setGeometry( + width() - _indicatorWidth - _indicatorSpacing, + (0.5 - _pressedLengthRatio / 2) * height(), + _indicatorWidth, + _pressedLengthRatio * height() + ); + } + else if (_hovered) { + _indicator->setGeometry( + width() - _indicatorWidth - _indicatorSpacing, + (0.5 - _hoveredLengthRatio / 2) * height(), + _indicatorWidth, + _hoveredLengthRatio * height() + ); + } + else { + _indicator->setGeometry( + width() - _indicatorWidth - _indicatorSpacing, + (0.5 - _activatedLengthRatio / 2) * height(), + _indicatorWidth, + _activatedLengthRatio * height() + ); + } + break; + case LUI_BTN_POS_TOP: + if (_pressed) { + _indicator->setGeometry( + (0.5 - _pressedLengthRatio / 2) * width(), + _indicatorSpacing, + _pressedLengthRatio * width(), + _indicatorWidth + ); + } + else if (_hovered) { + _indicator->setGeometry( + (0.5 - _hoveredLengthRatio / 2) * width(), + _indicatorSpacing, + _hoveredLengthRatio * width(), + _indicatorWidth + ); + } + else { + _indicator->setGeometry( + (0.5 - _activatedLengthRatio / 2) * width(), + _indicatorSpacing, + _activatedLengthRatio * width(), + _indicatorWidth + ); + } + break; + case LUI_BTN_POS_BOTTOM: + if (_pressed) { + _indicator->setGeometry( + (0.5 - _pressedLengthRatio / 2) * width(), + height() - _indicatorWidth - _indicatorSpacing, + _pressedLengthRatio * width(), + _indicatorWidth + ); + } + else if (_hovered) { + _indicator->setGeometry( + (0.5 - _hoveredLengthRatio / 2) * width(), + height() - _indicatorWidth - _indicatorSpacing, + _hoveredLengthRatio * width(), + _indicatorWidth + ); + } + else { + _indicator->setGeometry( + (0.5 - _activatedLengthRatio / 2) * width(), + height() - _indicatorWidth - _indicatorSpacing, + _activatedLengthRatio * width(), + _indicatorWidth + ); + } + break; + } +} + +QWidget* PushButton::childWidget() { + return _childWidget; +} + +void PushButton::setChildWidget(QWidget* widget) { + _childWidget = widget; + for (int i = 0; i < _stretchLayout->count(); i++) { + _stretchLayout->removeItem(_stretchLayout->itemAt(i)); + } + if (_childWidget != nullptr) { + _stretchLayout->addWidget(_childWidget); + _childWidget->show(); + _childWidgetOriginalGeometry = _childWidget->geometry(); + } +} + +bool PushButton::isSelected() const { + return _selected; +} diff --git a/FinalProject/pushbutton.h b/FinalProject/pushbutton.h new file mode 100644 index 0000000..8ac2f6d --- /dev/null +++ b/FinalProject/pushbutton.h @@ -0,0 +1,102 @@ +#pragma once + +#include +#include +#include +#include +#include + +class PushButton : public QWidget { + + Q_OBJECT + +public: + enum LUI_BTN_INDICATOR_POS { + LUI_BTN_POS_LEFT, + LUI_BTN_POS_RIGHT, + LUI_BTN_POS_TOP, + LUI_BTN_POS_BOTTOM + }; + +public: + PushButton(QWidget* child, QWidget* parent); + ~PushButton(); + +private: + // Button Layout + QWidget* _childWidget; + QRect _childWidgetOriginalGeometry; + const float _childWidgetShrinkRatio = 0.9; + QHBoxLayout* _stretchLayout; + QMargins _contentMargin = QMargins(12, 12, 12, 12); + + // Button ui + int _radius = 8; + + QWidget* _backgroundWidget; + const QColor _defaultColorScheme = QColor(58, 143, 183); + QColor _backgroundColor; + QColor _hoverColor; + QColor _pressedColor; + QColor _selectedColor; + + QWidget* _indicator; + LUI_BTN_INDICATOR_POS _indicatorPosition = LUI_BTN_POS_LEFT; + const int _indicatorWidth = 6; + const int _indicatorSpacing = 4; + const float _activatedLengthRatio = 0.4; + const float _hoveredLengthRatio = 0.5; + const float _pressedLengthRatio = 0.2; + QColor _indicatorColor; + QGraphicsOpacityEffect* _indicatorEffect; + + // Button state + bool _hovered = false; + bool _pressed = false; + bool _selected = false; + +private: + // UI util functions + void initializeUI(); + void generateColor(QColor colorScheme); + +private: + // Interactions + virtual void enterEvent(QEnterEvent* event) override; + virtual void leaveEvent(QEvent* event) override; + virtual void mousePressEvent(QMouseEvent* event) override; + virtual void mouseReleaseEvent(QMouseEvent* event) override; + virtual void resizeEvent(QResizeEvent* event) override; + +public: + // Operation APIs + void select(); + void deselect(); + + // Attribute setter APIs + void setRadius(int radius); + + void setBackgroundColor(QColor color); + void setHoverColor(QColor color); + void setPressedColor(QColor color); + void setSelectedColor(QColor color); + void setIndicatorColor(QColor color); + void setColorScheme(QColor primaryColor); + + QHBoxLayout* mainLayout() const; + void setAlignment(Qt::Alignment alignment); + void setMargin(QMargins margin); + void setMargin(int left, int top, int right, int bottom); + + void setIndicatorPosition(LUI_BTN_INDICATOR_POS position); + + QWidget* childWidget(); + void setChildWidget(QWidget* widget); + + bool isSelected() const; + +signals: + void onClick(); + void onHover(); + void onPressed(); +}; diff --git a/FinalProject/renderable.cpp b/FinalProject/renderable.cpp index 16eb8a2..8254edf 100644 --- a/FinalProject/renderable.cpp +++ b/FinalProject/renderable.cpp @@ -8,6 +8,10 @@ Renderable::Renderable(Model* model) : _model(model) {} Renderable::Renderable(Model* model, glm::vec3 position) : _model(model), _position(position) {} +void Renderable::setModel(Model* model) { + _model = model; +} + void Renderable::move(glm::vec3 deltaVec) { _position += deltaVec; } @@ -33,6 +37,11 @@ void Renderable::setScale(float scale) { } void Renderable::render(ShaderProgram shader) { + // Check if initialized + if (_model == nullptr) { + Logger::error("Tries to render an uninitialized renderable object"); + return; + } // Set model matrix shader.setUniform("model", modelMatrix()); // Render diff --git a/FinalProject/renderable.h b/FinalProject/renderable.h index 1ae9fe3..6409f1f 100644 --- a/FinalProject/renderable.h +++ b/FinalProject/renderable.h @@ -7,8 +7,16 @@ #include "shader.h" class Renderable { +public: + static Renderable empty() { + return Renderable(); + } + private: - Model* _model; + Renderable() {} + +private: + Model* _model = nullptr; glm::vec3 _position = glm::vec3(0.0f); glm::mat4 _rotation = glm::mat4(1.0f); glm::vec3 _scale = glm::vec3(1.0f); @@ -21,6 +29,7 @@ public: Renderable(Model* model, glm::vec3 position); public: + void setModel(Model* model); void move(glm::vec3 deltaVec); void setPosition(glm::vec3 position); void rotate(glm::vec3 axis, float deltaAngle); diff --git a/FinalProject/roundedcornerwidget.cpp b/FinalProject/roundedcornerwidget.cpp new file mode 100644 index 0000000..2f076fa --- /dev/null +++ b/FinalProject/roundedcornerwidget.cpp @@ -0,0 +1,106 @@ +#include +#include + +#include "roundedcornerwidget.h" + +RoundedCornerWidget::RoundedCornerWidget(QWidget* parent) : QWidget(parent) { + // Create main layout and main widget + _mainLayout = new QVBoxLayout(this); + _mainLayout->setContentsMargins(0, 1, 1, 1); + _mainLayout->setSpacing(0); + _mainWidget = new QWidget(this); + _mainWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + _mainLayout->addWidget(_mainWidget); + _mainWidget->show(); + setLayout(_mainLayout); +} + +RoundedCornerWidget::~RoundedCornerWidget() { +} + +void RoundedCornerWidget::showEvent(QShowEvent* event) { + // Call parent show event + QWidget::showEvent(event); + + if (_initialized) { + return; + } + + // Initialize widget UI + initializeWidgetUI(); + + // Set initialized state + _initialized = true; +} + +void RoundedCornerWidget::initializeWidgetUI() { + // Set up border widget + _borderWidget = new QWidget(this); + _borderWidget->setObjectName("borderWidget"); + QString borderWidgetStyleSheet = + "QWidget#borderWidget{background-color:#00FFFFFF;border:2px solid " + _borderColor.name(QColor::HexArgb) + ";" + "border-radius:" + QString::number(_cornerRadius) + "px;}"; + _borderWidget->setStyleSheet(borderWidgetStyleSheet); + _borderWidget->setAttribute(Qt::WA_TransparentForMouseEvents); + _borderWidget->move(0, 0); + _borderWidget->resize(width(), height()); + _borderWidget->show(); + + // Create a round cornered mask + QPainterPath path; + path.addRoundedRect(_mainWidget->rect(), _cornerRadius, _cornerRadius); + QRegion region(path.toFillPolygon().toPolygon()); + _mainWidget->setMask(region); +} + +void RoundedCornerWidget::resizeEvent(QResizeEvent* event) { + // Resize widget border + if (_borderWidget != nullptr) { + _borderWidget->move(0, 0); + _borderWidget->resize(width(), height()); + } + + // Resize window mask + if (_mainWidget != nullptr) { + QPainterPath path; + path.addRoundedRect(_mainWidget->rect(), _cornerRadius, _cornerRadius); + QRegion region(path.toFillPolygon().toPolygon()); + _mainWidget->setMask(region); + } +} + +void RoundedCornerWidget::setCornerRadius(int radius) { + // Set corner radius + _cornerRadius = radius; + + // Modify the corner radius in the current style sheet of the border widget + if (_borderWidget != nullptr) { + QString styleSheet = _borderWidget->styleSheet(); + styleSheet.replace(QRegularExpression("border-radius:\\d+px;"), + QString("border-radius:%1px;").arg(_cornerRadius)); + _borderWidget->setStyleSheet(styleSheet); + } + + // Reset the mask of the main widget + QPainterPath path; + path.addRoundedRect(_mainWidget->rect(), _cornerRadius, _cornerRadius); + QRegion region(path.toFillPolygon().toPolygon()); + _mainWidget->setMask(region); +} + +void RoundedCornerWidget::setBorderColor(QColor color) { + // Set color + _borderColor = color; + + // Modify the border color in the current style sheet of the border widget + if (_borderWidget != nullptr) { + QString styleSheet = _borderWidget->styleSheet(); + styleSheet.replace(QRegularExpression("border:2px solid #\\w+;"), + QString("border:2px solid %1;").arg(_borderColor.name(QColor::HexArgb))); + _borderWidget->setStyleSheet(styleSheet); + } +} + +QWidget* RoundedCornerWidget::mainWidget() const { + return _mainWidget; +} diff --git a/FinalProject/roundedcornerwidget.h b/FinalProject/roundedcornerwidget.h new file mode 100644 index 0000000..6c889d1 --- /dev/null +++ b/FinalProject/roundedcornerwidget.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include +#include + +class RoundedCornerWidget : public QWidget { + + Q_OBJECT + +public: + RoundedCornerWidget(QWidget* parent = 0); + ~RoundedCornerWidget(); + +protected: + // UI settings + int _cornerRadius = 15; + QColor _borderColor = QColor(255, 255, 255); + + // UI elements + QWidget* _mainWidget = nullptr; + QVBoxLayout* _mainLayout = nullptr; + QWidget* _borderWidget = nullptr; + + // Initialization mark + bool _initialized = false; + void initializeWidgetUI(); + + // UI events + virtual void showEvent(QShowEvent* event) override; + virtual void resizeEvent(QResizeEvent* event) override; + +public: + // Public APIs + void setCornerRadius(int radius); + void setBorderColor(QColor color); + QWidget* mainWidget() const; +}; diff --git a/FinalProject/sceneviewer.cpp b/FinalProject/sceneviewer.cpp index 6f60c35..ede88ab 100644 --- a/FinalProject/sceneviewer.cpp +++ b/FinalProject/sceneviewer.cpp @@ -17,6 +17,7 @@ using std::vector; SceneViewer::SceneViewer(QWidget* parent) : QOpenGLWidget(parent) { + // OpenGL initialize QSurfaceFormat format; format.setProfile(QSurfaceFormat::CoreProfile); format.setVersion(4, 3); @@ -64,15 +65,8 @@ void SceneViewer::initializeGL() { _shaderProgram.attachShader(fragmentShader); vertexShader.dispose(); fragmentShader.dispose(); - - Model* backpackModel = new Model("D:/code/ComputerGraphic/SceneEditor/obj/nanosuit/nanosuit.obj"); - Logger::info("Model loaded"); - Renderable backpack(backpackModel); - _objects.push_back(backpack); - - _camera.setPosition(glm::vec3(0.0f, 0.0f, 5.0f)); - + _camera.setPosition(glm::vec3(0.0f, 0.0f, 10.0f)); } void SceneViewer::resizeGL(int w, int h) { @@ -80,6 +74,8 @@ void SceneViewer::resizeGL(int w, int h) { } void SceneViewer::paintGL() { + Logger::debug("Repainting"); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); _shaderProgram.bind(); @@ -108,7 +104,9 @@ void SceneViewer::mousePressEvent(QMouseEvent* event) { } void SceneViewer::mouseMoveEvent(QMouseEvent* event) { - Logger::debug("Mouse moved with offset: " + std::to_string(event->x() - _lastMousePosition.x()) + ", " + std::to_string(event->y() - _lastMousePosition.y())); + if (event->buttons() != Qt::NoButton) { + Logger::debug("Mouse moved with offset: " + std::to_string(event->x() - _lastMousePosition.x()) + ", " + std::to_string(event->y() - _lastMousePosition.y())); + } // Check the type of button pressed switch (event->buttons()) { case Qt::LeftButton: { @@ -154,7 +152,7 @@ void SceneViewer::mouseMoveEvent(QMouseEvent* event) { // Update the last mouse position _lastMousePosition = event->pos(); // Update the view - update(); + parentWidget()->update(); } void SceneViewer::wheelEvent(QWheelEvent* event) { @@ -167,5 +165,31 @@ void SceneViewer::wheelEvent(QWheelEvent* event) { Logger::debug("New camera position: " + std::to_string(_camera.position().x) + ", " + std::to_string(_camera.position().y) + ", " + std::to_string(_camera.position().z)); Logger::debug("New center position: " + std::to_string(_rotateCenter.x) + ", " + std::to_string(_rotateCenter.y) + ", " + std::to_string(_rotateCenter.z)); // Update the view - update(); + parentWidget()->update(); +} + +void SceneViewer::showEvent(QShowEvent* event) { + // Call show event of super class + QOpenGLWidget::showEvent(event); + + if (_initialized) { + return; + } + + //// Create mask for rounded corner + //QPainterPath mask; + //mask.addRoundedRect(rect(), _cornerRadius, _cornerRadius); + //setMask(mask.toFillPolygon().toPolygon()); + + _initialized = true; +} + +void SceneViewer::resizeEvent(QResizeEvent* event) { + // Call resize event of super class + QOpenGLWidget::resizeEvent(event); + + //// Create mask for rounded corner + //QPainterPath mask; + //mask.addRoundedRect(rect(), _cornerRadius, _cornerRadius); + //setMask(mask.toFillPolygon().toPolygon()); } diff --git a/FinalProject/sceneviewer.h b/FinalProject/sceneviewer.h index a854b32..5d2460e 100644 --- a/FinalProject/sceneviewer.h +++ b/FinalProject/sceneviewer.h @@ -34,6 +34,10 @@ private: // User Interaction flags section--------------------- QPoint _lastMousePosition; Renderable* _selectedObject = nullptr; + + // UI interface control + const int _cornerRadius = 10; + bool _initialized = false; public: SceneViewer(QWidget* parent = 0); @@ -50,4 +54,7 @@ protected: virtual void mouseMoveEvent(QMouseEvent* event) override; virtual void wheelEvent(QWheelEvent* event) override; + // UI update events + virtual void showEvent(QShowEvent* event) override; + virtual void resizeEvent(QResizeEvent* event) override; }; \ No newline at end of file diff --git a/FinalProject/scrolllistwidget.cpp b/FinalProject/scrolllistwidget.cpp new file mode 100644 index 0000000..6d12181 --- /dev/null +++ b/FinalProject/scrolllistwidget.cpp @@ -0,0 +1,422 @@ +#include "scrolllistwidget.h" + +ScrollListWidget::ScrollListWidget(QWidget* parent) : QWidget(parent) +{ + //initialize list container and timer + container = new ScrollListContainer(this); + container->move(0, 0); + container->resize(this->width(), 3); + getCord = new QTimer; + getCord->setSingleShot(true); + rfrshView = new QTimer; + getCord->setSingleShot(true); + + indicator = new ScrollListIndicator(this); + indicator->resize(indicator->width(), (int)((double)this->height() * this->height() / (double)container->height())); + indicator->move(this->width() - indicator->width() - 3, 0); + + this->setMouseTracking(true); + container->setMouseTracking(true); + indicator->setMouseTracking(true); + + bounce = new QPropertyAnimation(container, "pos"); + + QObject::connect(getCord, SIGNAL(timeout()), this, SLOT(updateSpd())); + QObject::connect(rfrshView, SIGNAL(timeout()), this, SLOT(scrollContainer())); + QObject::connect(indicator, SIGNAL(scrollPage(int)), this, SLOT(scrollIndicator(int))); +} + +void ScrollListWidget::paintEvent(QPaintEvent* event) { + container->resize(this->width(), container->height()); + if (container->height() > this->height() && container->y() < this->height() - container->height() && curSpd == 0 && bounce->state() == QAbstractAnimation::Stopped) + container->move(container->x(), this->height() - container->height()); + if (container->height() <= this->height()) { + container->move(container->x(), 0); + indicator->hide(); + } + else { + indicator->show(); + } + indicator->resize(indicator->width(), (int)((double)this->height() * this->height() / (double)container->height())); + indicator->move(this->width() - indicator->width() - 3, -container->y() * this->height() / container->height()); +} + +void ScrollListWidget::mousePressEvent(QMouseEvent* event) { + if (container->height() > this->height()) { + if (container->y() <= 0 && container->y() + container->height() >= this->height()) + pressed = true; + lastY = event->pos().y(); + } + getCord->stop(); + rfrshView->stop(); + curSpd = 0; + outOfEdge = false; + moveStored = 0; + nextMove = 1; +} + +void ScrollListWidget::mouseMoveEvent(QMouseEvent* event) { + setCursor(Qt::ArrowCursor); + if (pressed) { + //start scroll + if (!getCord->isActive() && event->pos().y() - lastY != 0) { + //start 30ms timer + getCord->start(30); + strtY = event->pos().y(); + } + if (container->y() <= 0 && container->y() + container->height() >= this->height()) + container->move(container->x(), container->y() + event->pos().y() - lastY); + else { + if (!outOfEdge) { + bfEdgeY = event->pos().y(); + container->move(container->x(), container->y() + event->pos().y() - lastY); + outOfEdge = true; + } + else { + int pos = container->y() >= 0 ? 1 : -1; + int dp = event->pos().y() - bfEdgeY; + if (dp == 0) { + outOfEdge = false; + nextMove = 1; + moveStored = 0; + if (container->y() >= 0) + container->move(container->x(), 0); + else + container->move(container->x(), this->height() - container->height()); + } + else if (dp / abs(dp) != pos) { + outOfEdge = false; + container->move(container->x(), this->y() + event->pos().y() - bfEdgeY); + nextMove = 1; + moveStored = 0; + } + else { + while (abs(moveStored) + nextMove <= abs(event->pos().y() - bfEdgeY)) { + moveStored += nextMove * pos; + container->move(container->x(), container->y() + pos); + nextMove++; + } + while (nextMove > 1 && abs(moveStored) > abs(event->pos().y() - bfEdgeY)) { + nextMove--; + moveStored -= nextMove * pos; + container->move(container->x(), container->y() - pos); + } + if (moveStored == 0) { + outOfEdge = false; + if (container->y() >= 0) + container->move(container->x(), 0); + else + container->move(container->x(), this->height() - container->height()); + nextMove = 1; + moveStored = 0; + } + } + } + } + lastY = event->pos().y(); + } +} + +void ScrollListWidget::mouseReleaseEvent(QMouseEvent* event) { + //start scrolling + if (container->y() > 0 || container->y() + container->height() < this->height()) + bounceBack(); + else + rfrshView->start(30); + pressed = false; +} + +void ScrollListWidget::bounceBack() { + rfrshView->stop(); + getCord->stop(); + bounce->setDuration(500); + bounce->setStartValue(container->pos()); + if (container->y() > 0) + bounce->setEndValue(QPoint(container->x(), 0)); + else + bounce->setEndValue(QPoint(container->x(), this->height() - container->height())); + bounce->setEasingCurve(QEasingCurve::OutQuad); + bounce->start(); +} + +void ScrollListWidget::scrollContainer() { + //scroll + if (curSpd > 0) { + if (curSpd > MAXSPEED && !ignoreMaxSpeed) + curSpd = MAXSPEED; + else if (curSpd <= MAXSPEED) ignoreMaxSpeed = false; + int dp = scrollDown ? curSpd : -curSpd; + container->move(container->x(), container->y() + dp); + } + else + return; + if (container->y() <= 0 && container->y() + container->height() >= this->height()) { + curSpd -= damp; + curSpd = curSpd < 0 ? 0 : curSpd; + } + else + curSpd /= 2; + if (curSpd == 0 && (container->y() > 0 || container->y() + container->height() < this->height())) + bounceBack(); + else + rfrshView->start(30); +} + +void ScrollListWidget::updateSpd() { + int spd = lastY - strtY; + scrollDown = spd >= 0; + strtY = lastY; + curSpd = abs(spd); +} + +void ScrollListWidget::addWidget(QWidget* newWidget, bool setAnimation) { + newWidget->setParent(container); + container->AddWidget(newWidget, setAnimation); +} + +void ScrollListWidget::removeWidget(QWidget* w) { + container->RemoveWidget(w); +} + +void ScrollListWidget::scrollToTop() { + curSpd = sqrt(8 * (-container->pos().y()) + 2) / 2; + scrollDown = true; + getCord->stop(); + rfrshView->stop(); + outOfEdge = false; + moveStored = 0; + nextMove = 1; + ignoreMaxSpeed = true; + rfrshView->start(30); +} + +void ScrollListWidget::updateHeight() { + container->updateHeight(); +} + +void ScrollListWidget::clear() { + container->clear(); +} + +void ScrollListWidget::scrollIndicator(int dp) { + int newY = container->y() - dp * container->height() / this->height(); + if (newY > 0) + newY = 0; + else if (newY < this->height() - container->height()) + newY = this->height() - container->height(); + container->move(container->x(), newY); + update(); +} + +void ScrollListWidget::wheelEvent(QWheelEvent* event) { + if (container->y() > 0 || container->y() + container->height() < this->height()) + return; + curSpd += 5; + bool newDirection = event->angleDelta().y() > 0; + if (newDirection != scrollDown) + curSpd = 5; + if (curSpd > MAXSPEED) + curSpd = MAXSPEED; + scrollDown = newDirection; + if (!rfrshView->isActive()) + rfrshView->start(30); + update(); +} + +ScrollListContainer::ScrollListContainer(QWidget* parent) : QWidget(parent) {} + +void ScrollListContainer::paintEvent(QPaintEvent* event) { + for (int i = 0; i < widgets.size(); i++) { + widgets[i]->resize(this->width(), widgets[i]->height()); + } +} + +void ScrollListContainer::AddWidget(QWidget* widget, bool setAnimation) { + //Add animation for all widgets current + this->resize(this->width(), this->height() + widget->height() + spacing); + widgets.push_back(widget); + size++; + ys.push_back(0); + widget->resize(this->width(), widget->height()); + widget->show(); + + if (setAnimation) { + QGraphicsOpacityEffect* widgetOpac = new QGraphicsOpacityEffect(widget); + widgetOpac->setOpacity(0); + widget->setGraphicsEffect(widgetOpac); + QParallelAnimationGroup* dpGroup = new QParallelAnimationGroup; + QSequentialAnimationGroup* newWidgetFadeIn = new QSequentialAnimationGroup; + for (int i = 0; i < size - 1; i++) { + ys[i] += widget->height() + spacing; + QPropertyAnimation* move = new QPropertyAnimation(widgets[i], "pos"); + move->setDuration(750); + move->setStartValue(widgets[i]->pos()); + move->setEndValue(QPoint(widgets[i]->x(), ys[i])); + move->setEasingCurve(QEasingCurve::InOutQuart); + dpGroup->addAnimation(move); + } + newWidgetFadeIn->addPause(300); + QPropertyAnimation* fade = new QPropertyAnimation(widgetOpac, "opacity", widget); + fade->setDuration(300); + fade->setStartValue(0); + fade->setEndValue(0.99); + newWidgetFadeIn->addAnimation(fade); + dpGroup->addAnimation(newWidgetFadeIn); + dpGroup->start(); + connect(dpGroup, &QPropertyAnimation::stateChanged, [=]() { + if (dpGroup->state() == QAbstractAnimation::Stopped) { + if (widgetOpac->opacity() != 0.99) { + fade->start(QAbstractAnimation::DeleteWhenStopped); + connect(fade, &QPropertyAnimation::finished, [=]() {widgetOpac->deleteLater(); }); + } + else { + dpGroup->deleteLater(); + widgetOpac->deleteLater(); + } + } + }); + } + else { + for (int i = 0; i < size - 1; i++) { + ys[i] += widget->height() + spacing; + widgets[i]->move(QPoint(widgets[i]->pos().x(), ys[i])); + } + } +} + +void ScrollListContainer::RemoveWidget(QWidget* widget) { + int index; + if (widget == nullptr) { + index = size - 1; + if (index != -1) + widget = widgets[index]; + } + else + index = widgets.indexOf(widget); + if (index == -1 || widget == nullptr) { + return; + } + this->resize(this->width(), this->height() - widget->height() - spacing); + this->parentWidget()->update(); + widget->hide(); + widget->setParent(nullptr); + QParallelAnimationGroup* dpGroup = new QParallelAnimationGroup; + for (int i = index - 1; i >= 0; i--) { + ys[i] -= (widget->height() + spacing); + QPropertyAnimation* move = new QPropertyAnimation(widgets[i], "pos"); + move->setDuration(750); + move->setStartValue(widgets[i]->pos()); + move->setEndValue(QPoint(widgets[i]->x(), ys[i])); + move->setEasingCurve(QEasingCurve::InOutQuart); + dpGroup->addAnimation(move); + } + dpGroup->start(QAbstractAnimation::DeleteWhenStopped); + widgets.remove(index); + size--; + ys.remove(index); +} + +void ScrollListContainer::updateHeight() { + for (int i = size - 2; i >= 0; i--) { + ys[i] = ys[i + 1] + widgets[i + 1]->height() + spacing; + widgets[i]->move(widgets[i]->pos().x(), ys[i]); + } + this->resize(this->width(), ys[0] + widgets[0]->height() + 3); +} + +void ScrollListContainer::clear() { + int n = size; + for (int i = 0; i < n; i++) + RemoveWidget(); +} + +ScrollListIndicator::ScrollListIndicator(QWidget* parent) : QWidget(parent) +{ + this->resize(defaultWidth, 0); + hovTimer = new QTimer(this); + hovTimer->setSingleShot(true); + aniPause = new QTimer(this); + aniPause->setSingleShot(true); + QObject::connect(hovTimer, SIGNAL(timeout()), this, SLOT(setHoverActive())); + this->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + this->curColor = defaultColor; + + this->setMouseTracking(true); +} + +void ScrollListIndicator::paintEvent(QPaintEvent* event) { + QPainter painter(this); + painter.setPen(Qt::NoPen); + painter.setBrush(curColor); + painter.drawRect(this->rect()); +} + +void ScrollListIndicator::enterEvent(QEnterEvent* event) { + if (!pressed) { + hovTimer->start(100); + curColor = hoverColor; + update(); + } +} + +void ScrollListIndicator::leaveEvent(QEvent* event) { + hovTimer->stop(); + curColor = defaultColor; + QPropertyAnimation* narrow = new QPropertyAnimation(this, "geometry"); + narrow->setDuration(300); + narrow->setStartValue(QRect(this->x(), this->y(), this->width(), this->height())); + narrow->setEndValue(QRect(this->parentWidget()->width() - margin - defaultWidth, this->y(), defaultWidth, this->height())); + narrow->setEasingCurve(QEasingCurve::InOutQuad); + narrow->start(QAbstractAnimation::DeleteWhenStopped); + aniPause->start(300); + update(); +} + +void ScrollListIndicator::mousePressEvent(QMouseEvent* event) { + curColor = pressColor; + pressed = true; + //>note: globalPos -> globalPosition here due to deprecation + //> may cause issues +#if (QT_VERSION >= QT_VERSION_CHECK(6,0,0)) + lastY = event->globalPosition().y(); +#else + lastY = event->globalPos().y(); +#endif + update(); +} + +void ScrollListIndicator::mouseMoveEvent(QMouseEvent* event) { + if (pressed && !aniPause->isActive()) { + //>note: globalPos -> globalPosition here due to deprecation + //> may cause issues +#if (QT_VERSION >= QT_VERSION_CHECK(6,0,0)) + int dp = event->globalPosition().y() - lastY; +#else + int dp = event->globalPos().y() - lastY; +#endif + emit scrollPage(dp); + //>note: globalPos -> globalPosition here due to deprecation + //> may cause issues +#if (QT_VERSION >= QT_VERSION_CHECK(6,0,0)) + lastY = event->globalPosition().y(); +#else + lastY = event->globalPos().y(); +#endif + } +} + +void ScrollListIndicator::mouseReleaseEvent(QMouseEvent* event) { + pressed = false; + curColor = hoverColor; + update(); +} + +void ScrollListIndicator::setHoverActive() { + QPropertyAnimation* widen = new QPropertyAnimation(this, "geometry"); + widen->setDuration(300); + widen->setStartValue(QRect(this->x(), this->y(), this->width(), this->height())); + widen->setEndValue(QRect(this->parentWidget()->width() - margin - defaultWidthAtFocus, this->y(), defaultWidthAtFocus, this->height())); + widen->setEasingCurve(QEasingCurve::InOutQuad); + widen->start(QAbstractAnimation::DeleteWhenStopped); + aniPause->start(300); +} diff --git a/FinalProject/scrolllistwidget.h b/FinalProject/scrolllistwidget.h new file mode 100644 index 0000000..4ace7a1 --- /dev/null +++ b/FinalProject/scrolllistwidget.h @@ -0,0 +1,142 @@ +/* This Module has not yet been rewritten */ +/* Behavior and performance is not guaranteed*/ +/* @Linloir */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAXSPEED 70 + +class ScrollListWidget; +class ScrollListContainer; +class ScrollListIndicator; + +class ScrollListWidget : public QWidget +{ + Q_OBJECT + +private: + QTimer* getCord; + QTimer* rfrshView; + + ScrollListContainer* container; + ScrollListIndicator* indicator; + + QPropertyAnimation* bounce; + + bool pressed = false; + bool scrollDown = true; + bool outOfEdge = false; + bool ignoreMaxSpeed = false; + + int strtY; + int lastY; + int bfEdgeY; //last y value before out of edge + + int curSpd = 0; + int damp = 1; + int moveStored = 0; + int nextMove = 1; + + void paintEvent(QPaintEvent* event); + void mousePressEvent(QMouseEvent* event); + void mouseMoveEvent(QMouseEvent* event); + void mouseReleaseEvent(QMouseEvent* event); + void wheelEvent(QWheelEvent* event); + void bounceBack(); + + +public: + explicit ScrollListWidget(QWidget* parent = nullptr); + void addWidget(QWidget* newWidget, bool setAnimation = true); + void addWidgets(QVector widgets) { for (int i = 0; i < widgets.size(); i++)addWidget(widgets[i], false); } + void removeWidget(QWidget* w = nullptr); + void scrollToTop(); + void updateHeight(); + void clear(); + +signals: + +private slots: + void scrollContainer(); + void updateSpd(); + void scrollIndicator(int dp); + +}; + +class ScrollListContainer : public QWidget +{ + Q_OBJECT +public: + explicit ScrollListContainer(QWidget* parent = nullptr); + void AddWidget(QWidget* widget, bool setAnimation = true); + void RemoveWidget(QWidget* widget = nullptr); + void updateHeight(); + void clear(); + //void RemoveWidget(QWidget* widget); + +private: + //QTimer* newWidgetFade; + int spacing = 3; + QVector widgets; + int size = 0; + QVector ys; + + void paintEvent(QPaintEvent* event); + +signals: + +private slots: + +}; + +class ScrollListIndicator : public QWidget +{ + Q_OBJECT + +private: + QColor curColor; + QColor defaultColor = QColor(100, 100, 100, 130); + QColor hoverColor = QColor(70, 70, 70, 150); + QColor pressColor = QColor(50, 50, 50, 170); + + QTimer* hovTimer; + QTimer* aniPause; + + int lastY; + + int defaultWidth = 2; + int defaultWidthAtFocus = 9; + int margin = 3; + + bool pressed = false; + +public: + explicit ScrollListIndicator(QWidget* parent = nullptr); + +private: + void paintEvent(QPaintEvent* event); + void enterEvent(QEnterEvent* event); + void leaveEvent(QEvent* event); + void mousePressEvent(QMouseEvent* event); + void mouseMoveEvent(QMouseEvent* event); + void mouseReleaseEvent(QMouseEvent* event); + +signals: + void scrollPage(int); + +private slots: + void setHoverActive(); + +}; diff --git a/FinalProject/sidebar.cpp b/FinalProject/sidebar.cpp new file mode 100644 index 0000000..4ee8dd1 --- /dev/null +++ b/FinalProject/sidebar.cpp @@ -0,0 +1,286 @@ +#pragma once + +#include +#include +#include +#include + +#include "sidebar.h" + +SideBar::SideBar(QWidget* parent) + : QWidget(parent) +{ + setAttribute(Qt::WA_StyledBackground, true); + setMaximumWidth(_collapsedWidth); + + // Create stylesheet + setObjectName("sideBar"); + QString sideBarStyleSheet = "QWidget#sideBar{background-color:" + _backgroundColor.name(QColor::HexArgb) + "; }"; + setStyleSheet(sideBarStyleSheet); + + // Create main layout + _mainLayout = new QVBoxLayout(this); + _mainLayout->setSpacing(8); + _mainLayout->setContentsMargins(16, 16, 16, 16); + _mainLayout->setAlignment(Qt::AlignTop | Qt::AlignLeft); + _mainLayout->addSpacing(8); // Add the top spacing between the expand button and the window edge + + // Create expand button + _expandButton = new PushButton(nullptr, this); + _expandButton->setMargin(18, 16, 18, 16); + _expandButton->setIndicatorColor(QColor(255, 255, 255, 0)); // Set indicator to transparent + _expandButton->setHoverColor(QColor(0, 0, 0, 10)); + _expandButton->setPressedColor(QColor(0, 0, 0, 20)); + int buttonSize = _collapsedWidth - _mainLayout->contentsMargins().left() - _mainLayout->contentsMargins().right(); + int buttonSizeMax = _expandedWidth - _mainLayout->contentsMargins().left() - _mainLayout->contentsMargins().right(); + _expandButton->setMinimumSize(buttonSize, buttonSize); + _expandButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum); + _expandButtonIcon = new QLabel(_expandButton); + _expandButtonIcon->setFont(QFont("Font Awesome 6 Free Solid", 14)); + _expandButtonIcon->setText("\uf0c9"); + _expandButton->setChildWidget(_expandButtonIcon); + + // Connect the expand button event to expand / collapse event + connect(_expandButton, &PushButton::onClick, this, [=]() { + if (_expanded) { + collapse(); + } + else { + expand(); + } + }); + + // Add expand button to main layout + _mainLayout->addWidget(_expandButton); + _expandButton->show(); + + // Create the page icon button container widget + _pageIconButtonWidget = new QWidget(this); + _pageIconButtonWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + _pageIconButtonLayout = new QVBoxLayout(_pageIconButtonWidget); + _pageIconButtonLayout->setSpacing(8); + _pageIconButtonLayout->setContentsMargins(0, 0, 0, 0); + _pageIconButtonLayout->setAlignment(Qt::AlignTop | Qt::AlignLeft); + _pageIconButtonWidget->setLayout(_pageIconButtonLayout); + + // Create the page text button container widget + _pageTextButtonWidget = new QWidget(this); + _pageTextButtonWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + _pageTextButtonLayout = new QVBoxLayout(_pageTextButtonWidget); + _pageTextButtonLayout->setSpacing(8); + _pageTextButtonLayout->setContentsMargins(0, 0, 0, 0); + _pageTextButtonLayout->setAlignment(Qt::AlignTop | Qt::AlignLeft); + _pageTextButtonWidget->setLayout(_pageTextButtonLayout); + + // Add page icon and text button container widgets to main layout + _mainLayout->addWidget(_pageIconButtonWidget); + _mainLayout->addWidget(_pageTextButtonWidget); + + // Display the icon buttons + _pageIconButtonWidget->show(); + _pageTextButtonWidget->hide(); +} + +SideBar::~SideBar() {} + +void SideBar::expand() { + // Add animation to expand the side bar + QPropertyAnimation* expandAnimation = new QPropertyAnimation(this, "maximumWidth"); + expandAnimation->setDuration(650); + expandAnimation->setEasingCurve(QEasingCurve::OutExpo); + expandAnimation->setStartValue(width()); + expandAnimation->setEndValue(_expandedWidth); + expandAnimation->start(QAbstractAnimation::DeleteWhenStopped); + + // Show the page text buttons + _pageIconButtonWidget->hide(); + _pageTextButtonWidget->show(); + + // Set the expand state + _expanded = true; +} + +void SideBar::collapse() { + // Add animation to collapse the side bar + QPropertyAnimation* collapseAnimation = new QPropertyAnimation(this, "maximumWidth"); + collapseAnimation->setDuration(650); + collapseAnimation->setEasingCurve(QEasingCurve::OutExpo); + collapseAnimation->setStartValue(width()); + collapseAnimation->setEndValue(_collapsedWidth); + collapseAnimation->start(QAbstractAnimation::DeleteWhenStopped); + + // Show the page icon buttons + _pageIconButtonWidget->show(); + _pageTextButtonWidget->hide(); + + // Set the expand state + _expanded = false; +} + +void SideBar::setBackgroundColor(QColor color) { + _backgroundColor = color; + QString sideBarStyleSheet = "QWidget#sideBar{background-color:" + _backgroundColor.name(QColor::HexArgb) + "; }"; + setStyleSheet(sideBarStyleSheet); +} + +void SideBar::setExpandedWidth(int width) { + if (width <= 0) { + return; + } + _expandedWidth = width; +} + +void SideBar::setCollapsedWidth(int width) { + if (width <= 0) { + return; + } + _collapsedWidth = width; +} + +void SideBar::selectPage(PageWidget* page) { + // Check for input validity + if (page == nullptr) { + return; + } + + // Deselect current page if there is one + if (_currentPage != nullptr) { + // Find the buttons of the current page + int index = _pageList.indexOf(_currentPage); + PushButton* currentPageIconButton = _pageButtonList.at(index).first; + PushButton* currentPageTextButton = _pageButtonList.at(index).second; + + // Deselect both the buttons + currentPageIconButton->deselect(); + currentPageTextButton->deselect(); + } + + // Temporarily store the current page + PageWidget* previousPage = _currentPage; + + // Select the new page + int index = _pageList.indexOf(page); + PushButton* newPageIconButton = _pageButtonList.at(index).first; + PushButton* newPageTextButton = _pageButtonList.at(index).second; + newPageIconButton->select(); + newPageTextButton->select(); + _currentPage = page; + + // Emit the page selected signal + emit onPageChanged(previousPage, page); +} + +void SideBar::addPage(PageWidget* page) { + // Call on the page's get button method to get icon button & text button + PushButton* iconButton = page->getPageIconButton(_pageIconButtonWidget); + PushButton* textButton = page->getPageTextButton(_pageTextButtonWidget); + + // Store the page in the page list + _pageList.append(page); + + // Store the corresponding buttons + _pageButtonList.append(QPair(iconButton, textButton)); + + // Add the icon button to the icon button layout + _pageIconButtonLayout->addWidget(iconButton); + iconButton->show(); + + // Add the text button to the text button layout + _pageTextButtonLayout->addWidget(textButton); + textButton->show(); + + // Resize the buttons and set size policy to fixed + int buttonSize = _collapsedWidth - _mainLayout->contentsMargins().left() - _mainLayout->contentsMargins().right(); + int buttonSizeMax = _expandedWidth - _mainLayout->contentsMargins().left() - _mainLayout->contentsMargins().right(); + iconButton->setMinimumSize(buttonSize, buttonSize); + textButton->setMinimumSize(buttonSize, buttonSize); + iconButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + textButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + + // Connect the button click event with page selection + connect(iconButton, &PushButton::onClick, this, [=]() { + selectPage(page); + }); + connect(textButton, &PushButton::onClick, this, [=]() { + selectPage(page); + }); + + // Select the page if there are no pages + if (_currentPage == nullptr) { + selectPage(page); + } +} + +void SideBar::insertPage(PageWidget* page, int index) { + // Call on the page's get button method to get icon button & text button + PushButton* iconButton = page->getPageIconButton(_pageIconButtonWidget); + PushButton* textButton = page->getPageTextButton(_pageTextButtonWidget); + + // Store the page + _pageList.insert(index, page); + + // Store the corresponding buttons + _pageButtonList.insert(index, QPair(iconButton, textButton)); + + // Add the icon button to the icon button layout + _pageIconButtonLayout->insertWidget(index, iconButton); + iconButton->show(); + + // Add the text button to the text button layout + _pageTextButtonLayout->insertWidget(index, textButton); + textButton->show(); + + // Resize the buttons and set size policy to fixed + int buttonSize = _collapsedWidth - _mainLayout->contentsMargins().left() - _mainLayout->contentsMargins().right(); + int buttonSizeMax = _expandedWidth - _mainLayout->contentsMargins().left() - _mainLayout->contentsMargins().right(); + iconButton->setMinimumSize(buttonSize, buttonSize); + textButton->setMinimumSize(buttonSize, buttonSize); + iconButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + textButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + + // Connect the button click event with page selection + connect(iconButton, &PushButton::onClick, this, [=]() { + selectPage(page); + }); + connect(textButton, &PushButton::onClick, this, [=]() { + selectPage(page); + }); + + // Select the page if there are no pages + if (_currentPage == nullptr) { + selectPage(page); + } +} + +void SideBar::removePage(PageWidget* page) { + // Get the index of the page from the page list + int index = _pageList.indexOf(page); + + // Get the corresponding buttons + PushButton* iconButton = _pageButtonList.at(index).first; + PushButton* textButton = _pageButtonList.at(index).second; + + // Remove the page from the list + _pageList.removeAt(index); + + // Remove the icon and text buttons from the list + _pageButtonList.removeAt(index); + + // Remove the icon button from the icon button layout + _pageIconButtonLayout->removeWidget(iconButton); + iconButton->hide(); + + // Remove the text button from the text button layout + _pageTextButtonLayout->removeWidget(textButton); + textButton->hide(); + + // Select the first page if the current page is the page being removed + if (_currentPage == page) { + if (_pageList.size() > 0) { + selectPage(_pageList.first()); + } + else { + _currentPage = nullptr; + } + } +} diff --git a/FinalProject/sidebar.h b/FinalProject/sidebar.h new file mode 100644 index 0000000..7ebfe07 --- /dev/null +++ b/FinalProject/sidebar.h @@ -0,0 +1,63 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "pushbutton.h" +#include "pagewidget.h" + +class SideBar : public QWidget +{ + Q_OBJECT + +public: + SideBar(QWidget* parent = 0); + ~SideBar(); + +private: + // Initialized flag + bool _initialized = false; + + // UI control variables + QColor _backgroundColor = QColor(230, 230, 230); + int _collapsedWidth = 86; + int _expandedWidth = 200; + + // UI widgets + PushButton* _expandButton; + QLabel* _expandButtonIcon; + QVBoxLayout* _mainLayout = nullptr; // Layout expand icon and page selection buttons + QWidget* _pageIconButtonWidget = nullptr; // Container for page icon buttons + QVBoxLayout* _pageIconButtonLayout = nullptr; // Layout page icon buttons + QWidget* _pageTextButtonWidget = nullptr; // Container for page text buttons + QVBoxLayout* _pageTextButtonLayout = nullptr; // Layout page text buttons + + // Interaction controls + bool _expanded = false; + PageWidget* _currentPage = nullptr; + + // State storage + // QMap> _pageButtonMap; + QList _pageList; + QList> _pageButtonList; + +private: + void expand(); + void collapse(); + void selectPage(PageWidget* page); + +public: + void setBackgroundColor(QColor color); + void setCollapsedWidth(int width); + void setExpandedWidth(int width); + void addPage(PageWidget* page); + void insertPage(PageWidget* page, int index); + void removePage(PageWidget* page); + +signals: + void onPageChanged(PageWidget* previousPage, PageWidget* currentPage); +};