diff --git a/FinalProject/Deng.ttf b/FinalProject/Deng.ttf new file mode 100644 index 0000000..1eb188e Binary files /dev/null and b/FinalProject/Deng.ttf differ diff --git a/FinalProject/Dengb.ttf b/FinalProject/Dengb.ttf new file mode 100644 index 0000000..4873453 Binary files /dev/null and b/FinalProject/Dengb.ttf differ diff --git a/FinalProject/Dengl.ttf b/FinalProject/Dengl.ttf new file mode 100644 index 0000000..3d6ca1f Binary files /dev/null and b/FinalProject/Dengl.ttf differ diff --git a/FinalProject/FinalProject.vcxproj b/FinalProject/FinalProject.vcxproj index d35a107..fa35487 100644 --- a/FinalProject/FinalProject.vcxproj +++ b/FinalProject/FinalProject.vcxproj @@ -114,6 +114,7 @@ + @@ -127,6 +128,7 @@ + @@ -157,8 +159,10 @@ + + @@ -167,7 +171,7 @@ - + diff --git a/FinalProject/FinalProject.vcxproj.filters b/FinalProject/FinalProject.vcxproj.filters index 31e4ab0..9409877 100644 --- a/FinalProject/FinalProject.vcxproj.filters +++ b/FinalProject/FinalProject.vcxproj.filters @@ -174,6 +174,12 @@ Source Files\OpenGL Abstractions + + Source Files\Qt Widgets\GUI Components + + + Source Files\Qt Widgets\Pages\Scene Editor\Object Setter + @@ -212,9 +218,6 @@ Header Files\Utils - - Header Files\Qt Widgets\Pages\Scene Editor\Object Setter - Header Files\OpenGL Abstractions @@ -274,6 +277,15 @@ Header Files\Qt Widgets\Pages\Scene Editor\Object Selector + + Header Files\Qt Widgets\Pages\Scene Editor\Object Setter + + + Header Files\Qt Widgets\GUI Components + + + Header Files\Qt Widgets\Pages\Scene Editor\Object Setter + diff --git a/FinalProject/corbel.ttf b/FinalProject/corbel.ttf new file mode 100644 index 0000000..34fd5aa Binary files /dev/null and b/FinalProject/corbel.ttf differ diff --git a/FinalProject/editorpage.cpp b/FinalProject/editorpage.cpp index fc720af..6dfdd98 100644 --- a/FinalProject/editorpage.cpp +++ b/FinalProject/editorpage.cpp @@ -36,6 +36,12 @@ EditorPage::EditorPage(QWidget* parent) : _modelSelector = new ModelSelector(_mainWidget); _mainLayout->addWidget(_modelSelector); _modelSelector->show(); + + // Generate editing layout + _editingLayout = new QVBoxLayout(_mainWidget); + _editingLayout->setContentsMargins(0, 0, 0, 0); + _editingLayout->setSpacing(16); + _mainLayout->addLayout(_editingLayout); // Generate scene viewer _sceneViewerContainer = new RoundedCornerWidget(_mainWidget); @@ -47,11 +53,27 @@ EditorPage::EditorPage(QWidget* parent) : _sceneViewer = new SceneViewer(_sceneViewerContainer->mainWidget()); _sceneViewerContainerLayout->addWidget(_sceneViewer); _sceneViewer->show(); - _mainLayout->addWidget(_sceneViewerContainer); + _editingLayout->addWidget(_sceneViewerContainer); _sceneViewerContainer->show(); + // Generate model setter + _modelSetter = new ModelSetter(_mainWidget); + _modelSetter->setMaximumHeight(150); + _editingLayout->addWidget(_modelSetter); + _modelSetter->show(); + _modelSetter->setObjectName("ModelSetter"); + _modelSetter->setStyleSheet("#ModelSetter { background-color: #f0f0f0; border-radius: 10px; }"); + // Connect signals connect(_modelSelector, &ModelSelector::onObjectSelected, _sceneViewer, &SceneViewer::addObject); + connect(_sceneViewer, &SceneViewer::onSelect, _modelSetter, &ModelSetter::update); + connect(_sceneViewer, &SceneViewer::onUpdate, _modelSetter, &ModelSetter::update); + connect(_modelSetter, &ModelSetter::onAdjustStart, _sceneViewer, &SceneViewer::setDragFlag); + connect(_modelSetter, &ModelSetter::onAdjustEnd, _sceneViewer, &SceneViewer::clearDragFlag); + connect(_modelSetter, &ModelSetter::onAdjust, this, [=]() { + _sceneViewer->update(); + }); + connect(_modelSetter, &ModelSetter::onDeleteObject, _sceneViewer, &SceneViewer::deleteObject); } EditorPage::~EditorPage() {} diff --git a/FinalProject/editorpage.h b/FinalProject/editorpage.h index acf41cf..3b28202 100644 --- a/FinalProject/editorpage.h +++ b/FinalProject/editorpage.h @@ -8,6 +8,7 @@ #include "sceneviewer.h" #include "roundedcornerwidget.h" #include "modelselector.h" +#include "modelsetter.h" class EditorPage : public PageWidget { @@ -35,10 +36,14 @@ private: ModelSelector* _modelSelector = nullptr; + QVBoxLayout* _editingLayout = nullptr; + RoundedCornerWidget* _sceneViewerContainer = nullptr; QVBoxLayout* _sceneViewerContainerLayout = nullptr; SceneViewer* _sceneViewer = nullptr; + ModelSetter* _modelSetter = nullptr; + public: virtual PushButton* getPageIconButton(QWidget* context) override; virtual PushButton* getPageTextButton(QWidget* context) override; diff --git a/FinalProject/fragmentshader.glsl b/FinalProject/fragmentshader.glsl index 648e6c4..11a251f 100644 --- a/FinalProject/fragmentshader.glsl +++ b/FinalProject/fragmentshader.glsl @@ -30,8 +30,8 @@ struct PointLight { struct SpotLight { vec3 position; vec3 direction; - float cutOff; - float outerCutOff; + float innercutoff; + float outercutoff; float constant; float linear; @@ -143,8 +143,8 @@ vec3 CalcSpotLight(SpotLight light, vec3 normal, vec3 fragPos, vec3 viewDir) float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance)); // spotlight intensity float theta = dot(lightDir, normalize(-light.direction)); - float epsilon = light.cutOff - light.outerCutOff; - float intensity = clamp((theta - light.outerCutOff) / epsilon, 0.0, 1.0); + float epsilon = light.innercutoff - light.outercutoff; + float intensity = clamp((theta - light.outercutoff) / epsilon, 0.0, 1.0); // combine results vec3 ambient = light.ambient * vec3(texture(material.texture_diffuse1, TexCoords)); vec3 diffuse = light.diffuse * diff * vec3(texture(material.texture_diffuse1, TexCoords)); diff --git a/FinalProject/main.cpp b/FinalProject/main.cpp index 1b8bb85..ca8deb9 100644 --- a/FinalProject/main.cpp +++ b/FinalProject/main.cpp @@ -11,6 +11,10 @@ int main(int argc, char *argv[]) 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 + QFontDatabase::addApplicationFont(":/fonts/corbel.ttf"); // Add corbel font to application + QFontDatabase::addApplicationFont(":/fonts/Deng.ttf"); // Add Deng Xian font to application + QFontDatabase::addApplicationFont(":/fonts/Dengb.ttf"); // Add Deng Xian Bold font to application + QFontDatabase::addApplicationFont(":/fonts/Dengl.ttf"); // Add Deng Xian Light font to application MainWindow w; w.setMouseTracking(true); diff --git a/FinalProject/mainwindow.qrc b/FinalProject/mainwindow.qrc index 9b8663a..53f6c03 100644 --- a/FinalProject/mainwindow.qrc +++ b/FinalProject/mainwindow.qrc @@ -14,5 +14,9 @@ font_awesome_6_regular_free.otf font_awesome_6_solid_free.otf + corbel.ttf + Deng.ttf + Dengb.ttf + Dengl.ttf diff --git a/FinalProject/modelattrslide.cpp b/FinalProject/modelattrslide.cpp new file mode 100644 index 0000000..2d839e9 --- /dev/null +++ b/FinalProject/modelattrslide.cpp @@ -0,0 +1,79 @@ +#include "modelattrslide.h" + +ModelAttributeSlide::ModelAttributeSlide(const QString& label, float min, float max, int step, QWidget* parent) : + QWidget(parent) +{ + // Create main stretch layout + _stretchLayout = new QHBoxLayout(this); + _stretchLayout->setContentsMargins(0, 0, 0, 0); + _stretchLayout->setSpacing(0); + setLayout(_stretchLayout); + + // Create Slider + _slider = new Slider(min, max, step, this); + _stretchLayout->addWidget(_slider); + _slider->show(); + + // Create Label + _label = new QLabel(label, this); + _label->setMinimumWidth(56); + _label->setFont(QFont("Corbel", 11)); + _label->show(); + _slider->mainLayout()->insertWidget(0, _label); + _slider->mainLayout()->insertSpacing(1, 8); + + // Create Value label + _val = new QLabel(this); + _val->setMinimumWidth(32); + _val->setFont(QFont("Corbel", 11)); + _val->setText(QString::number(_slider->val(), 'f', 1)); + _val->show(); + _slider->mainLayout()->addSpacing(8); + _slider->mainLayout()->addWidget(_val); + + // Connect + connect(_slider, &Slider::onChanged, this, &ModelAttributeSlide::onChanged); + connect(_slider, &Slider::onSetValue, this, [=]() { + _val->setText(QString::number(_slider->val(), 'f', 1)); + }); + connect(_slider, &Slider::onDragStart, this, &ModelAttributeSlide::onChangeStart); + connect(_slider, &Slider::onDragEnd, this, &ModelAttributeSlide::onChangeEnd); +} + +ModelAttributeSlide::~ModelAttributeSlide() +{} + +void ModelAttributeSlide::setLabel(const QString& label) +{ + _label->setText(label); +} + +void ModelAttributeSlide::setMin(float min) +{ + _slider->setMin(min); +} + +void ModelAttributeSlide::setMax(float max) +{ + _slider->setMax(max); +} + +void ModelAttributeSlide::setStep(float step) +{ + _slider->setStep(step); +} + +void ModelAttributeSlide::setValue(float val) +{ + _slider->setValue(val); +} + +void ModelAttributeSlide::setTransformation(std::function transform, std::function inverse) +{ + _slider->setTransformation(transform, inverse); +} + +void ModelAttributeSlide::setEnabled(bool enable) +{ + _slider->setEnabled(enable); +} diff --git a/FinalProject/modelattrslide.h b/FinalProject/modelattrslide.h new file mode 100644 index 0000000..a7bda63 --- /dev/null +++ b/FinalProject/modelattrslide.h @@ -0,0 +1,42 @@ +#pragma once + +#include +#include +#include + +#include "slider.h" + +class ModelAttributeSlide : public QWidget +{ + Q_OBJECT + +public: + ModelAttributeSlide(const QString& label, float min, float max, int step, QWidget* parent = 0); + ~ModelAttributeSlide(); + +private: + QHBoxLayout* _stretchLayout; + QLabel* _label; + QLabel* _val; + Slider* _slider; + +public: + // Getter APIs + float val() const { return _slider->val(); } + float lev() const { return _slider->lev(); } + + // Setter APIs + void setLabel(const QString& label); + void setMin(float min); + void setMax(float max); + void setStep(float max); + void setValue(float val); // Set the actual value + void setTransformation(std::function transform, std::function inverse); + + void setEnabled(bool enable = true); + +signals: + void onChanged(float val); + void onChangeStart(); + void onChangeEnd(); +}; \ No newline at end of file diff --git a/FinalProject/modelsetter.cpp b/FinalProject/modelsetter.cpp index 6f70f09..9c4c1c1 100644 --- a/FinalProject/modelsetter.cpp +++ b/FinalProject/modelsetter.cpp @@ -1 +1,532 @@ -#pragma once +#include "modelsetter.h" + +ModelSetter::ModelSetter(QWidget* parent) : ModelSetter(nullptr, parent) {} + +ModelSetter::ModelSetter(Renderable* object, QWidget* parent) : + QWidget(parent), _object(object) +{ + // Set background color and border radius + setAttribute(Qt::WA_StyledBackground, true); + + // Create main layout + _mainLayout = new QHBoxLayout(this); + _mainLayout->setContentsMargins(12, 8, 12, 8); + _mainLayout->setSpacing(8); + setLayout(_mainLayout); + + // Create Object Settings Panel + { + // Create container widget + QWidget* _objectSettingPanel = new QWidget(this); + _objectSettingPanel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + _mainLayout->addWidget(_objectSettingPanel); + _objectSettingPanel->show(); + + // Create container widget layout + _objectSettingLayout = new QVBoxLayout(_objectSettingPanel); + _objectSettingLayout->setContentsMargins(0, 0, 0, 0); + _objectSettingLayout->setSpacing(4); + _objectSettingPanel->setLayout(_objectSettingLayout); + + // Create attribute adjusters + _scale = new ModelAttributeSlide("Scale", 0.1, 10, 100, _objectSettingPanel); + _rotateX = new ModelAttributeSlide("RotateX", 0, 360, 3600, _objectSettingPanel); + _rotateY = new ModelAttributeSlide("RotateY", 0, 360, 3600, _objectSettingPanel); + _rotateZ = new ModelAttributeSlide("RotateZ", 0, 360, 3600, _objectSettingPanel); + + // Add attribute adjusters to layout + _objectSettingLayout->addWidget(_scale); + _objectSettingLayout->addWidget(_rotateX); + _objectSettingLayout->addWidget(_rotateY); + _objectSettingLayout->addWidget(_rotateZ); + _scale->show(); + _rotateX->show(); + _rotateY->show(); + _rotateZ->show(); + + // Connect + connect(_scale, &ModelAttributeSlide::onChangeStart, this, &ModelSetter::onAdjustStart); + connect(_scale, &ModelAttributeSlide::onChangeEnd, this, &ModelSetter::onAdjustEnd); + connect(_scale, &ModelAttributeSlide::onChanged, this, [=]() { + if (_object != nullptr) { + _object->setScale(_scale->val()); + emit onAdjust(); + } + }); + connect(_scale, &ModelAttributeSlide::onChangeEnd, this, [=]() { + if (_object != nullptr) { + _object->updateBoundary(); + } + }); + + connect(_rotateX, &ModelAttributeSlide::onChangeStart, this, &ModelSetter::onAdjustStart); + connect(_rotateX, &ModelAttributeSlide::onChangeEnd, this, &ModelSetter::onAdjustEnd); + connect(_rotateX, &ModelAttributeSlide::onChanged, this, [=]() { + if (_object != nullptr) { + setRotate(); + emit onAdjust(); + } + }); + connect(_rotateX, &ModelAttributeSlide::onChangeEnd, this, [=]() { + if (_object != nullptr) { + _object->updateBoundary(); + } + }); + + connect(_rotateY, &ModelAttributeSlide::onChangeStart, this, &ModelSetter::onAdjustStart); + connect(_rotateY, &ModelAttributeSlide::onChangeEnd, this, &ModelSetter::onAdjustEnd); + connect(_rotateY, &ModelAttributeSlide::onChanged, this, [=]() { + if (_object != nullptr) { + setRotate(); + emit onAdjust(); + } + }); + connect(_rotateY, &ModelAttributeSlide::onChangeEnd, this, [=]() { + if (_object != nullptr) { + _object->updateBoundary(); + } + }); + + connect(_rotateZ, &ModelAttributeSlide::onChangeStart, this, &ModelSetter::onAdjustStart); + connect(_rotateZ, &ModelAttributeSlide::onChangeEnd, this, &ModelSetter::onAdjustEnd); + connect(_rotateZ, &ModelAttributeSlide::onChanged, this, [=]() { + if (_object != nullptr) { + setRotate(); + emit onAdjust(); + } + }); + connect(_rotateZ, &ModelAttributeSlide::onChangeEnd, this, [=]() { + if (_object != nullptr) { + _object->updateBoundary(); + } + }); + } + + // Create light switches + { + // Create container widget + _lightSwitchPanel = new QWidget(this); + _lightSwitchPanel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding); + _lightSwitchPanel->setMinimumWidth(36); + _mainLayout->addWidget(_lightSwitchPanel); + _lightSwitchPanel->show(); + + // Create container widget layout + _lightSwitchLayout = new QVBoxLayout(_lightSwitchPanel); + _lightSwitchLayout->setContentsMargins(0, 0, 0, 0); + _lightSwitchLayout->setAlignment(Qt::AlignCenter); + _lightSwitchLayout->setSpacing(4); + _lightSwitchPanel->setLayout(_lightSwitchLayout); + + // Create light switches + _lightSwitch = new PushButton(nullptr, _lightSwitchPanel); + _lightSwitch->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding); + _lightSwitch->setIndicatorPosition(PushButton::LUI_BTN_POS_BOTTOM); + _lightSwitchIcon = new QLabel(_lightSwitch); + _lightSwitchIcon->setFont(QFont("Font Awesome 6 Free Regular", 12)); + _lightSwitchIcon->setText("\uf0eb"); + _lightSwitchIcon->setAlignment(Qt::AlignCenter); + _lightSwitch->setChildWidget(_lightSwitchIcon); + + _lightColorPanel = new PushButton(nullptr, _lightSwitchPanel); + _lightColorPanel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding); + _lightColorPanel->setIndicatorPosition(PushButton::LUI_BTN_POS_BOTTOM); + _lightColorPanelIcon = new QLabel(_lightColorPanel); + _lightColorPanelIcon->setFont(QFont("Font Awesome 6 Free Solid", 12)); + _lightColorPanelIcon->setText("\uf53f"); + _lightColorPanelIcon->setAlignment(Qt::AlignCenter); + _lightColorPanel->setStyleSheet("QWidget#indicator{border: 1px solid #5c5c5c;}"); + _lightColorPanel->setChildWidget(_lightColorPanelIcon); + + // Add light switches to layout + _lightSwitchLayout->addWidget(_lightSwitch); + _lightSwitchLayout->addWidget(_lightColorPanel); + _lightSwitch->show(); + _lightColorPanel->show(); + + // Connect + connect(_lightSwitch, &PushButton::onClick, this, [=]() { + if (_object != nullptr && _object->hasLight()) { + // Disable light + _object->disposeLight(); + _lightSwitch->deselect(); + _lightSwitchIcon->setFont(QFont("Font Awesome 6 Free Regular", 12)); + _lightColorPanel->setEnabled(false); + _lightDistance->setEnabled(false); + _lightRotateTheta->setEnabled(false); + _lightRotatePhi->setEnabled(false); + _lightCutoffAngle->setEnabled(false); + _lightR->setEnabled(false); + _lightG->setEnabled(false); + _lightB->setEnabled(false); + _lightColorPanel->deselect(); + _lightSettingPanel->show(); + _lightColorSettingPanel->hide(); + _colorPaletteOn = false; + emit onAdjust(); + } + else if (_object != nullptr) { + // Enable light + _object->makeLight(); + _lightSwitch->select(); + _lightSwitchIcon->setFont(QFont("Font Awesome 6 Free Solid", 12)); + _lightColorPanel->setEnabled(true); + _lightColorPanel->setColorScheme(QColor(_object->originalLight()->lightColor().x, _object->originalLight()->lightColor().y, _object->originalLight()->lightColor().z)); + _lightDistance->setEnabled(true); + _lightCutoffAngle->setEnabled(true); + _lightR->setEnabled(true); + _lightG->setEnabled(true); + _lightB->setEnabled(true); + _lightR->setValue(_object->originalLight()->lightColor().r * 255.0f); + _lightG->setValue(_object->originalLight()->lightColor().g * 255.0f); + _lightB->setValue(_object->originalLight()->lightColor().b * 255.0f); + _lightColorPanel->setColorScheme(QColor( + _object->originalLight()->lightColor().r * 255.0f, + _object->originalLight()->lightColor().g * 255.0f, + _object->originalLight()->lightColor().b * 255.0f + )); + _lightColorPanel->select(); + _lightDistance->setValue(_object->originalLight()->idealDistance()); + _lightRotateTheta->setValue(glm::degrees(glm::acos(_object->originalLight()->lightDirection().y))); + _lightRotatePhi->setValue(glm::degrees(glm::atan(_object->originalLight()->lightDirection().x / _object->originalLight()->lightDirection().z))); + _lightCutoffAngle->setValue(_object->originalLight()->cutOffAngle()); + if (_lightCutoffAngle->val() != 180.0f) { + _lightRotateTheta->setEnabled(true); + _lightRotatePhi->setEnabled(true); + } + else { + _lightRotateTheta->setEnabled(false); + _lightRotatePhi->setEnabled(false); + } + emit onAdjust(); + } + }); + connect(_lightColorPanel, &PushButton::onClick, this, [=]() { + if (_object != nullptr && _object->hasLight()) { + if (!_colorPaletteOn) { + _lightSettingPanel->hide(); + _lightColorSettingPanel->show(); + _colorPaletteOn = true; + } + else { + _lightSettingPanel->show(); + _lightColorSettingPanel->hide(); + _colorPaletteOn = false; + } + } + }); + } + + // Create light setting panel + { + // Create container widget + _lightSettingPanel = new QWidget(this); + _lightSettingPanel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + _mainLayout->addWidget(_lightSettingPanel); + _lightSettingPanel->show(); + + // Create container widget layout + _lightSettingLayout = new QVBoxLayout(_lightSettingPanel); + _lightSettingLayout->setContentsMargins(0, 0, 0, 0); + _lightSettingLayout->setSpacing(4); + _lightSettingPanel->setLayout(_lightSettingLayout); + + // Create attribute adjusters + _lightDistance = new ModelAttributeSlide("Distance", 10, 3025, 145, _lightSettingPanel); + _lightDistance->setTransformation( + [](float x) { + float y; + if (x <= 90) { + y = 10 + x; + } + else { + y = 100 + glm::pow(x - 90, 2.0f); + } + return y; + }, + [](float y) { + float x; + if (y <= 100) { + x = y - 10; + } + else { + x = glm::sqrt(y - 100) + 90; + } + return x; + } + ); + _lightRotateTheta = new ModelAttributeSlide("Rotate\u03B8", 0, 360, 3600, _lightSettingPanel); + _lightRotatePhi = new ModelAttributeSlide("Rotate\u03C6", 0, 360, 3600, _lightSettingPanel); + _lightCutoffAngle = new ModelAttributeSlide("Cutoff", 0, 180, 1800, _lightSettingPanel); + + // Add attribute adjusters to layout + _lightSettingLayout->addWidget(_lightDistance); + _lightSettingLayout->addWidget(_lightRotateTheta); + _lightSettingLayout->addWidget(_lightRotatePhi); + _lightSettingLayout->addWidget(_lightCutoffAngle); + _lightDistance->show(); + _lightRotateTheta->show(); + _lightRotatePhi->show(); + _lightCutoffAngle->show(); + + // Connect + connect(_lightDistance, &ModelAttributeSlide::onChanged, this, [=]() { + if (_object != nullptr && _object->hasLight()) { + _object->originalLight()->setIdealDistance(_lightDistance->val()); + emit onAdjust(); + } + }); + connect(_lightRotateTheta, &ModelAttributeSlide::onChanged, this, [=]() { + if (_object != nullptr && _object->hasLight()) { + setLightDir(); + emit onAdjust(); + } + }); + connect(_lightRotatePhi, &ModelAttributeSlide::onChanged, this, [=]() { + if (_object != nullptr && _object->hasLight()) { + setLightDir(); + emit onAdjust(); + } + }); + connect(_lightCutoffAngle, &ModelAttributeSlide::onChanged, this, [=]() { + if (_object != nullptr && _object->hasLight()) { + _object->originalLight()->setCutOffAngle(_lightCutoffAngle->val()); + if (_lightCutoffAngle->val() != 180.0f) { + _lightRotateTheta->setEnabled(true); + _lightRotatePhi->setEnabled(true); + } + else { + _lightRotateTheta->setEnabled(false); + _lightRotatePhi->setEnabled(false); + } + emit onAdjust(); + } + }); + } + + // Create color setting panel + { + // Create container widget + _lightColorSettingPanel = new QWidget(this); + _lightColorSettingPanel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + _mainLayout->addWidget(_lightColorSettingPanel); + _lightColorSettingPanel->show(); + + // Create container widget layout + _lightColorSettingPanelLayout = new QVBoxLayout(_lightColorSettingPanel); + _lightColorSettingPanelLayout->setContentsMargins(0, 0, 0, 0); + _lightColorSettingPanelLayout->setSpacing(8); + _lightColorSettingPanel->setLayout(_lightColorSettingPanelLayout); + + // Create color adjusters + _lightR = new ModelAttributeSlide("R", 0, 255, 2550, _lightColorSettingPanel); + _lightG = new ModelAttributeSlide("G", 0, 255, 2550, _lightColorSettingPanel); + _lightB = new ModelAttributeSlide("B", 0, 255, 2550, _lightColorSettingPanel); + + // Add color adjusters to layout + _lightColorSettingPanelLayout->addWidget(_lightR); + _lightColorSettingPanelLayout->addWidget(_lightG); + _lightColorSettingPanelLayout->addWidget(_lightB); + _lightR->show(); + _lightG->show(); + _lightB->show(); + + // Connect + connect(_lightR, &ModelAttributeSlide::onChanged, this, [=]() { + if (_object != nullptr && _object->hasLight()) { + setLightColor(); + _lightColorPanel->setColorScheme(QColor(_lightR->val(), _lightG->val(), _lightB->val())); + emit onAdjust(); + } + }); + connect(_lightG, &ModelAttributeSlide::onChanged, this, [=]() { + if (_object != nullptr && _object->hasLight()) { + setLightColor(); + _lightColorPanel->setColorScheme(QColor(_lightR->val(), _lightG->val(), _lightB->val())); + emit onAdjust(); + } + }); + connect(_lightB, &ModelAttributeSlide::onChanged, this, [=]() { + if (_object != nullptr && _object->hasLight()) { + setLightColor(); + _lightColorPanel->setColorScheme(QColor(_lightR->val(), _lightG->val(), _lightB->val())); + emit onAdjust(); + } + }); + } + + // Create delete button + { + _deleteBtn = new PushButton(nullptr, this); + _deleteBtn->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + _deleteIcon = new QLabel(_deleteBtn); + _deleteIcon->setFont(QFont("Font Awesome 6 Free Regular", 12)); + _deleteIcon->setText("\uf2ed"); + _deleteIcon->setAlignment(Qt::AlignCenter); + _deleteBtn->setChildWidget(_deleteIcon); + _deleteBtn->setColorScheme(QColor(171, 59, 58)); + _deleteBtn->setIndicatorColor(QColor(171, 59, 58, 0)); + + // Add delete button to layout + _mainLayout->addWidget(_deleteBtn); + _deleteBtn->show(); + + // Connect + connect(_deleteBtn, &PushButton::onClick, this, [=]() { + emit onDeleteObject(); + }); + } + + // Update values + update(_object); +} + +ModelSetter::~ModelSetter() {} + +void ModelSetter::setRotate() { + if (_object == nullptr) { + return; + } + // Around X-axis + _object->setRotation(glm::vec3(1.0f, 0.0f, 0.0f), _rotateX->val()); + // Around Y-axis + _object->rotate(glm::vec3(0.0f, 1.0f, 0.0f), _rotateY->val()); + // Around Z-axis + _object->rotate(glm::vec3(0.0f, 0.0f, 1.0f), _rotateZ->val()); +} + +void ModelSetter::setLightDir() { + if (_object == nullptr || !_object->hasLight()) { + return; + } + _object->originalLight()->setLightDirection(glm::normalize(glm::vec3( + cos(glm::radians(_lightRotateTheta->val())) * sin(glm::radians(_lightRotatePhi->val())), + sin(glm::radians(_lightRotateTheta->val())), + cos(glm::radians(_lightRotateTheta->val())) * cos(glm::radians(_lightRotatePhi->val())) + ))); +} + +void ModelSetter::setLightColor() { + if (_object == nullptr || !_object->hasLight()) { + return; + } + _object->originalLight()->setLightColor(glm::vec3(_lightR->val() / 255.0f, _lightG->val() / 255.0f, _lightB->val() / 255.0f)); +} + +void ModelSetter::update(Renderable* object) { + // Update settings panel by object's real value + if (object == nullptr) { + // Disable all settings + _scale->setEnabled(false); + _rotateX->setEnabled(false); + _rotateY->setEnabled(false); + _rotateZ->setEnabled(false); + _lightDistance->setEnabled(false); + _lightRotateTheta->setEnabled(false); + _lightRotatePhi->setEnabled(false); + _lightCutoffAngle->setEnabled(false); + _lightR->setEnabled(false); + _lightG->setEnabled(false); + _lightB->setEnabled(false); + _lightSwitch->setEnabled(false); + _lightColorPanel->setEnabled(false); + _deleteBtn->setEnabled(false); + _lightSwitchIcon->setFont(QFont("Font Awesome 6 Free Regular", 12)); + _lightSwitch->deselect(); + _lightColorPanel->deselect(); + _deleteBtn->setEnabled(false); + } + else { + // Update settings + if (_object == nullptr) { + _scale->setEnabled(); + } + _scale->setValue(object->scaleVal().x); + + if (_object == nullptr) { + _rotateX->setEnabled(); + _rotateY->setEnabled(); + _rotateZ->setEnabled(); + } + // Extract x, y, z axis rotation from rotation matrix + glm::mat4 rotationMatrix = object->rotation(); + float rotateX = glm::degrees(glm::asin(-rotationMatrix[1][2])); + float rotateY = glm::degrees(glm::atan(rotationMatrix[0][2] / rotationMatrix[2][2])); + float rotateZ = glm::degrees(glm::atan(rotationMatrix[1][0] / rotationMatrix[1][1])); + _rotateX->setValue(rotateX); + _rotateY->setValue(rotateY); + _rotateZ->setValue(rotateZ); + + if (_object == nullptr) { + _lightSwitch->setEnabled(); + } + if (object->hasLight()) { + _lightSwitch->select(); + } + else { + _lightSwitch->deselect(); + } + + if (object->hasLight()) { + if (_object == nullptr || !_object->hasLight()) { + // Enable light related settings + _lightDistance->setEnabled(); + _lightRotateTheta->setEnabled(); + _lightRotatePhi->setEnabled(); + _lightCutoffAngle->setEnabled(); + _lightR->setEnabled(); + _lightG->setEnabled(); + _lightB->setEnabled(); + _lightColorPanel->setEnabled(); + _lightSwitchIcon->setFont(QFont("Font Awesome 6 Free Solid", 12)); + } + // Update light related settings + _lightDistance->setValue(object->originalLight()->idealDistance()); + _lightRotateTheta->setValue(glm::degrees(glm::acos(object->originalLight()->lightDirection().y))); + _lightRotatePhi->setValue(glm::degrees(glm::atan(object->originalLight()->lightDirection().x / object->originalLight()->lightDirection().z))); + _lightCutoffAngle->setValue(object->originalLight()->cutOffAngle()); + if (_lightCutoffAngle->val() != 180.0f) { + _lightRotateTheta->setEnabled(true); + _lightRotatePhi->setEnabled(true); + } + else { + _lightRotateTheta->setEnabled(false); + _lightRotatePhi->setEnabled(false); + } + _lightR->setValue(object->originalLight()->lightColor().r * 255.0f); + _lightG->setValue(object->originalLight()->lightColor().g * 255.0f); + _lightB->setValue(object->originalLight()->lightColor().b * 255.0f); + _lightColorPanel->select(); + _lightColorPanel->setColorScheme(QColor( + object->originalLight()->lightColor().r * 255.0f, + object->originalLight()->lightColor().g * 255.0f, + object->originalLight()->lightColor().b * 255.0f + )); + } + else { + if (_object != nullptr && _object->hasLight()) { + // Disable light related settings + _lightDistance->setEnabled(false); + _lightRotateTheta->setEnabled(false); + _lightRotatePhi->setEnabled(false); + _lightCutoffAngle->setEnabled(false); + _lightR->setEnabled(false); + _lightG->setEnabled(false); + _lightB->setEnabled(false); + _lightColorPanel->setEnabled(false); + _lightSwitchIcon->setFont(QFont("Font Awesome 6 Free Regular", 12)); + _lightSwitch->deselect(); + _lightColorPanel->deselect(); + } + } + _deleteBtn->setEnabled(); + } + + if (_object != object || _object == nullptr) { + _colorPaletteOn = false; + _lightColorSettingPanel->hide(); + _lightSettingPanel->show(); + } + + _object = object; +} diff --git a/FinalProject/modelsetter.h b/FinalProject/modelsetter.h index 71fd808..7a65ae3 100644 --- a/FinalProject/modelsetter.h +++ b/FinalProject/modelsetter.h @@ -1,3 +1,73 @@ #pragma once #include +#include +#include + +#include "renderable.h" +#include "pushbutton.h" +#include "modelattrslide.h" + +class ModelSetter : public QWidget +{ + Q_OBJECT + +public: + ModelSetter(QWidget* parent = 0); + ModelSetter(Renderable* object, QWidget* parent = 0); + ~ModelSetter(); + +private: + // UI Elemenets + QHBoxLayout* _mainLayout; + + QWidget* _objectSettingPanel; + QVBoxLayout* _objectSettingLayout; + + QWidget* _lightSettingButtons; + QVBoxLayout* _lightSettingsButtonsLayout; + + QWidget* _lightSettingPanel; + QVBoxLayout* _lightSettingLayout; + + QWidget* _lightColorSettingPanel; + QVBoxLayout* _lightColorSettingPanelLayout; + + ModelAttributeSlide* _scale; + ModelAttributeSlide* _rotateX; + ModelAttributeSlide* _rotateY; + ModelAttributeSlide* _rotateZ; + ModelAttributeSlide* _lightDistance; + ModelAttributeSlide* _lightRotateTheta; + ModelAttributeSlide* _lightRotatePhi; + ModelAttributeSlide* _lightCutoffAngle; + ModelAttributeSlide* _lightR; + ModelAttributeSlide* _lightG; + ModelAttributeSlide* _lightB; + QWidget* _lightSwitchPanel; + QVBoxLayout* _lightSwitchLayout; + PushButton* _lightSwitch; + QLabel* _lightSwitchIcon; + PushButton* _lightColorPanel; + QLabel* _lightColorPanelIcon; + PushButton* _deleteBtn; + QLabel* _deleteIcon; + + // State + Renderable* _object = nullptr; + bool _colorPaletteOn = false; + +private: + void setRotate(); + void setLightDir(); + void setLightColor(); + +public: + void update(Renderable* object); + +signals: + void onAdjustStart(); + void onAdjustEnd(); + void onAdjust(); + void onDeleteObject(); +}; diff --git a/FinalProject/pushbutton.cpp b/FinalProject/pushbutton.cpp index 0470b86..5ed448d 100644 --- a/FinalProject/pushbutton.cpp +++ b/FinalProject/pushbutton.cpp @@ -84,6 +84,10 @@ void PushButton::generateColor(QColor colorScheme) { } void PushButton::enterEvent(QEnterEvent* event) { + if (!_enabled) { + return; + } + setCursor(Qt::PointingHandCursor); _backgroundWidget->setStyleSheet("QWidget#backgroundWidget{background-color:" + _hoverColor.name(QColor::HexArgb) + ";border-radius:" + QString::number(_radius) + "px;}"); @@ -141,6 +145,10 @@ void PushButton::enterEvent(QEnterEvent* event) { } void PushButton::leaveEvent(QEvent* event) { + if (!_enabled) { + return; + } + setCursor(Qt::ArrowCursor); if (_selected) { @@ -203,6 +211,10 @@ void PushButton::leaveEvent(QEvent* event) { } void PushButton::mousePressEvent(QMouseEvent* event) { + if (!_enabled) { + return; + } + _backgroundWidget->setStyleSheet("QWidget#backgroundWidget{background-color:" + _pressedColor.name(QColor::HexArgb) + ";border-radius:" + QString::number(_radius) + "px;}"); QPropertyAnimation* indicatorShrinkLength = new QPropertyAnimation(_indicator, "geometry", this); @@ -601,6 +613,49 @@ void PushButton::deselect() { _selected = false; } +void PushButton::setEnabled(bool enabled) { + if (enabled == _enabled) { + return; + } + + if (enabled) { + _enabled = true; + // Restore colors + _backgroundColor = _restoredColor[0]; + _hoverColor = _restoredColor[1]; + _pressedColor = _restoredColor[2]; + _selectedColor = _restoredColor[3]; + _indicatorColor = _restoredColor[4]; + 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;}"); + } + else { + _enabled = false; + _pressed = false; + _hovered = false; + // Store color + _restoredColor[0] = _backgroundColor; + _restoredColor[1] = _hoverColor; + _restoredColor[2] = _pressedColor; + _restoredColor[3] = _selectedColor; + _restoredColor[4] = _indicatorColor; + // Set disabled colors + setColorScheme(QColor(200, 200, 200)); + } +} + void PushButton::setRadius(int radius) { // get current style sheet QString styleSheet = _backgroundWidget->styleSheet(); diff --git a/FinalProject/pushbutton.h b/FinalProject/pushbutton.h index 8ac2f6d..ab96288 100644 --- a/FinalProject/pushbutton.h +++ b/FinalProject/pushbutton.h @@ -39,6 +39,7 @@ private: QColor _hoverColor; QColor _pressedColor; QColor _selectedColor; + QColor _restoredColor[5]; QWidget* _indicator; LUI_BTN_INDICATOR_POS _indicatorPosition = LUI_BTN_POS_LEFT; @@ -51,6 +52,7 @@ private: QGraphicsOpacityEffect* _indicatorEffect; // Button state + bool _enabled = true; bool _hovered = false; bool _pressed = false; bool _selected = false; @@ -72,6 +74,7 @@ public: // Operation APIs void select(); void deselect(); + void setEnabled(bool enabled = true); // Attribute setter APIs void setRadius(int radius); diff --git a/FinalProject/renderable.cpp b/FinalProject/renderable.cpp index 9f890d5..219558e 100644 --- a/FinalProject/renderable.cpp +++ b/FinalProject/renderable.cpp @@ -59,6 +59,13 @@ void Renderable::makeLight() { _light = new ScopedLight(glm::vec3(0.0f)); } +void Renderable::disposeLight() { + if (_light != nullptr) { + delete _light; + _light = nullptr; + } +} + void Renderable::render(ShaderProgram shader) { // Check if initialized if (_model == nullptr) { diff --git a/FinalProject/renderable.h b/FinalProject/renderable.h index e8b268b..eed1222 100644 --- a/FinalProject/renderable.h +++ b/FinalProject/renderable.h @@ -32,6 +32,10 @@ public: ~Renderable(); public: + glm::vec3 position() const { return _position; } + glm::mat4 rotation() const { return _rotation; } + glm::vec3 scaleVal() const { return _scale; } + void setModel(Model* model); void move(glm::vec3 deltaVec); void setPosition(glm::vec3 position); @@ -44,6 +48,7 @@ public: ScopedLight* originalLight() const; // pass out the light object to scene manager to set light attributes bool hasLight() const { return _light != nullptr; } void makeLight(); // create a light source in the object + void disposeLight(); // remove the light source in the object const Boundary& boundary() const { return _boundary; } diff --git a/FinalProject/sceneviewer.cpp b/FinalProject/sceneviewer.cpp index c16781a..dc915eb 100644 --- a/FinalProject/sceneviewer.cpp +++ b/FinalProject/sceneviewer.cpp @@ -280,6 +280,7 @@ void SceneViewer::mouseReleaseEvent(QMouseEvent* event) { else { _selectedObject = _pressedObject; _hideBound = false; + emit onSelect(_selectedObject); } // Reset pressed object @@ -314,6 +315,7 @@ void SceneViewer::mouseMoveEvent(QMouseEvent* event) { _selectedObject->rotate(_camera.up(), delta.x * 0.01f); // Rotate around camera right _selectedObject->rotate(_camera.right(), delta.y * 0.01f); + emit onUpdate(_selectedObject); } break; } @@ -332,6 +334,7 @@ void SceneViewer::mouseMoveEvent(QMouseEvent* event) { // Scale object glm::vec2 delta = glm::vec2(event->x() - _lastMousePosition.x(), event->y() - _lastMousePosition.y()); _selectedObject->scale(-delta.y * 0.01f); + emit onUpdate(_selectedObject); } else { // Set dragged @@ -356,6 +359,7 @@ void SceneViewer::mouseMoveEvent(QMouseEvent* event) { } else { moveOperatingObject(ray); + emit onUpdate(_selectedObject); } break; } @@ -490,4 +494,29 @@ void SceneViewer::addObject(Model* model) { _operatingObject = newObject; _objects.push_back(newObject); parentWidget()->update(); + emit onSelect(_selectedObject); +} + +void SceneViewer::deleteObject() { + if (_selectedObject == nullptr) { + return; + } + makeCurrent(); + for (auto it = _objects.begin(); it != _objects.end(); ++it) { + if (*it == _selectedObject) { + _objects.erase(it); + break; + } + } + delete _selectedObject; + if (_hoveredObject == _selectedObject) { + _hoveredObject = nullptr; + } + if (_pressedObject == _selectedObject) { + _pressedObject = nullptr; + } + _selectedObject = nullptr; + _operatingObject = nullptr; + emit onSelect(nullptr); + parentWidget()->update(); } diff --git a/FinalProject/sceneviewer.h b/FinalProject/sceneviewer.h index 1c0b855..71585e1 100644 --- a/FinalProject/sceneviewer.h +++ b/FinalProject/sceneviewer.h @@ -86,6 +86,19 @@ protected: virtual void keyPressEvent(QKeyEvent* event) override; virtual void keyReleaseEvent(QKeyEvent* event) override; +public: + void setDragFlag() { + _hideBound = true; + parentWidget()->update(); + } + void clearDragFlag() { + _hideBound = false; + if (_selectedObject != nullptr) + _selectedObject->updateBoundary(); + parentWidget()->update(); + } + void deleteObject(); + signals: void onHover(Renderable* object); void onSelect(Renderable* object); diff --git a/FinalProject/slider.cpp b/FinalProject/slider.cpp new file mode 100644 index 0000000..2a154fc --- /dev/null +++ b/FinalProject/slider.cpp @@ -0,0 +1,288 @@ +#include + +#include "slider.h" + +Slider::Slider(float min, float max, int step, QWidget* parent) : + QWidget(parent), _min(min), _max(max), _step(step) +{ + // Set map functions + _transformFunc = [this](float x) { + // Map x of [0, step] to [_min, _max] + float y = x / _step * (_max - _min) + _min; + return y; + }; + _inversionFunc = [this](float y) { + // Map y of [_min, _max] to [0, step] + float x = (y - _min) / (_max - _min) * _step; + return x; + }; + // Generate colors + generateColor(_defaultSchemeColor); + // Create main layout + _mainLayout = new QHBoxLayout(this); + _mainLayout->setContentsMargins(4, 4, 4, 4); + _mainLayout->setSpacing(0); + setLayout(_mainLayout); + // Create slider + _slider = new QSlider(Qt::Horizontal, this); + _slider->setMinimum(0); + _slider->setMaximum(_step); + _slider->setSingleStep(1); + // Set slider style sheet + QString grooveStyle = "QSlider::groove:horizontal {" + "height:6px;" + "border-radius:3px;" + "}"; + QString sliderStyle = "QSlider::handle:horizontal {" + "width:12px;" + "margin-bottom:-3px;" + "margin-top:-3px;" + "background:" + _handleColor.name(QColor::HexArgb) + ";" + "border-radius:6px;" + "}"; + QString sliderHoverStyle = "QSlider::handle:horizontal:hover {" + "width:12px;" + "margin-bottom:-3px;" + "margin-top:-3px;" + "background:" + _hoverColor.name(QColor::HexArgb) + ";" + "border-radius:6px;" + "}"; + QString sliderPressStyle = "QSlider::handle:horizontal:pressed {" + "width:12px;" + "margin-bottom:-3px;" + "margin-top:-3px;" + "background:" + _pressColor.name(QColor::HexArgb) + ";" + "border-radius:6px;" + "}"; + QString subStyle = "QSlider::sub-page:horizontal {" + "background:" + _subColor.name(QColor::HexArgb) + ";" + "border-radius:3px;" + "}"; + QString addStyle = "QSlider::add-page:horizontal {" + "background:" + _addColor.name(QColor::HexArgb) + ";" + "border-radius:3px;" + "}"; + _slider->setStyleSheet(grooveStyle + sliderStyle + sliderHoverStyle + sliderPressStyle + subStyle + addStyle); + // Create decrease button + _decreaseBtn = new PushButton(nullptr, this); + _decreaseBtn->setColorScheme(_defaultSchemeColor); + _decreaseBtn->setFixedSize(24, 24); + _decreaseBtn->setRadius(8); + _decreaseBtn->setMargin(0, 0, 0, 3); + _decreaseBtn->setIndicatorColor(QColor(255, 255, 255, 0)); + // Create decrease label + _decreaseIcon = new QLabel(_decreaseBtn); + _decreaseIcon->setFont(QFont("Font Awesome 6 Free Solid", 6)); + _decreaseIcon->setText("\uf068"); + _decreaseIcon->setAlignment(Qt::AlignCenter); + _decreaseBtn->setChildWidget(_decreaseIcon); + _decreaseIcon->show(); + // Create increase button + _increaseBtn = new PushButton(nullptr, this); + _increaseBtn->setColorScheme(_defaultSchemeColor); + _increaseBtn->setFixedSize(24, 24); + _increaseBtn->setRadius(8); + _increaseBtn->setMargin(0, 0, 0, 3); + _increaseBtn->setIndicatorColor(QColor(255, 255, 255, 0)); + // Create increase label + _increaseIcon = new QLabel(_increaseBtn); + _increaseIcon->setFont(QFont("Font Awesome 6 Free Solid", 6)); + _increaseIcon->setText("\uf067"); + _increaseIcon->setAlignment(Qt::AlignCenter); + _increaseBtn->setChildWidget(_increaseIcon); + _increaseIcon->show(); + // Add to main layout + _mainLayout->addWidget(_decreaseBtn); + _mainLayout->addSpacing(4); + _mainLayout->addWidget(_slider); + _mainLayout->addSpacing(4); + _mainLayout->addWidget(_increaseBtn); + _decreaseBtn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + _slider->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + _increaseBtn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + _decreaseBtn->show(); + _slider->show(); + _increaseBtn->show(); + // Connect signals and slots + connect(_decreaseBtn, &PushButton::onClick, this, [this]() { + // Set current value + _slider->setValue(_slider->value() - 1); + emit onChanged(_transformFunc(_slider->value())); + emit onSetValue(_transformFunc(_slider->value())); + emit onDragEnd(); + }); + connect(_increaseBtn, &PushButton::onClick, this, [this]() { + // Set current value + _slider->setValue(_slider->value() + 1); + emit onChanged(_transformFunc(_slider->value())); + emit onSetValue(_transformFunc(_slider->value())); + emit onDragEnd(); + }); + connect(_slider, &QSlider::valueChanged, this, [this](int value) { + // Judge whether the slider is changed by dragging or function + if (_slider->isSliderDown()) { + // Value changed by user + emit onChanged(_transformFunc(value)); + } + emit onSetValue(_transformFunc(value)); + }); + connect(_slider, &QSlider::sliderPressed, this, &Slider::onDragStart); + connect(_slider, &QSlider::sliderReleased, this, &Slider::onDragEnd); +} + +Slider::~Slider() +{} + +void Slider::generateColor(QColor schemeColor) { + _subColor = schemeColor; + _addColor = QColor(216, 216, 216); + _handleColor = QColor(194, 194, 194); + _hoverColor = schemeColor.lighter(20); + float hoverBlendRatio = 0.2; + _hoverColor = QColor( + _hoverColor.red() * hoverBlendRatio + _handleColor.red() * (1 - hoverBlendRatio), + _hoverColor.green() * hoverBlendRatio + _handleColor.green() * (1 - hoverBlendRatio), + _hoverColor.blue() * hoverBlendRatio + _handleColor.blue() * (1 - hoverBlendRatio) + ); + _pressColor = schemeColor.lighter(20); + float pressBlendRatio = 0.5; + _pressColor = QColor( + _pressColor.red() * pressBlendRatio + _handleColor.red() * (1 - pressBlendRatio), + _pressColor.green() * pressBlendRatio + _handleColor.green() * (1 - pressBlendRatio), + _pressColor.blue() * pressBlendRatio + _handleColor.blue() * (1 - pressBlendRatio) + ); +} + +void Slider::setColorScheme(QColor color) { + generateColor(color); + // Change style sheet + QString grooveStyle = "QSlider::groove:horizontal {" + "height:6px;" + "border-radius:3px;" + "}"; + QString sliderStyle = "QSlider::handle:horizontal {" + "width:12px;" + "margin-bottom:-3px;" + "margin-top:-3px;" + "background:" + _handleColor.name(QColor::HexArgb) + ";" + "border-radius:6px;" + "}"; + QString sliderHoverStyle = "QSlider::handle:horizontal:hover {" + "width:12px;" + "margin-bottom:-3px;" + "margin-top:-3px;" + "background:" + _hoverColor.name(QColor::HexArgb) + ";" + "border-radius:6px;" + "}"; + QString sliderPressStyle = "QSlider::handle:horizontal:pressed {" + "width:12px;" + "margin-bottom:-3px;" + "margin-top:-3px;" + "background:" + _pressColor.name(QColor::HexArgb) + ";" + "border-radius:6px;" + "}"; + QString subStyle = "QSlider::sub-page:horizontal {" + "background:" + _subColor.name(QColor::HexArgb) + ";" + "border-radius:3px;" + "}"; + QString addStyle = "QSlider::add-page:horizontal {" + "background:" + _addColor.name(QColor::HexArgb) + ";" + "border-radius:3px;" + "}"; + _slider->setStyleSheet(grooveStyle + sliderStyle + sliderHoverStyle + sliderPressStyle + subStyle + addStyle); + // Change button color + _decreaseBtn->setColorScheme(color); + _decreaseBtn->setIndicatorColor(QColor(255, 255, 255, 0)); + _increaseBtn->setColorScheme(color); + _increaseBtn->setIndicatorColor(QColor(255, 255, 255, 0)); +} + +void Slider::setMin(float min) { + _min = min; +} + +void Slider::setMax(float max) { + _max = max; +} + +void Slider::setStep(float step) { + _step = step; + _slider->setMaximum(step); +} + +void Slider::setValue(float value) { + _slider->setValue((int)_inversionFunc(value)); +} + +void Slider::setTransformation(std::function transformFunc, std::function inversionFunc) { + _transformFunc = transformFunc; + _inversionFunc = inversionFunc; +} + +void Slider::setEnabled(bool enabled) { + if (enabled == _enabled) { + return; + } + _enabled = enabled; + _slider->setEnabled(enabled); + _decreaseBtn->setEnabled(enabled); + _increaseBtn->setEnabled(enabled); + if (!enabled) { + // Store colors + _restoredColor[0] = _subColor; + _restoredColor[1] = _addColor; + _restoredColor[2] = _handleColor; + _restoredColor[3] = _hoverColor; + _restoredColor[4] = _pressColor; + // Change colors + setColorScheme(QColor(200, 200, 200)); + } + else { + // Restore colors + _subColor = _restoredColor[0]; + _addColor = _restoredColor[1]; + _handleColor = _restoredColor[2]; + _hoverColor = _restoredColor[3]; + _pressColor = _restoredColor[4]; + // Change style sheet + QString grooveStyle = "QSlider::groove:horizontal {" + "height:6px;" + "border-radius:3px;" + "}"; + QString sliderStyle = "QSlider::handle:horizontal {" + "width:12px;" + "margin-bottom:-3px;" + "margin-top:-3px;" + "background:" + _handleColor.name(QColor::HexArgb) + ";" + "border-radius:6px;" + "}"; + QString sliderHoverStyle = "QSlider::handle:horizontal:hover {" + "width:12px;" + "margin-bottom:-3px;" + "margin-top:-3px;" + "background:" + _hoverColor.name(QColor::HexArgb) + ";" + "border-radius:6px;" + "}"; + QString sliderPressStyle = "QSlider::handle:horizontal:pressed {" + "width:12px;" + "margin-bottom:-3px;" + "margin-top:-3px;" + "background:" + _pressColor.name(QColor::HexArgb) + ";" + "border-radius:6px;" + "}"; + QString subStyle = "QSlider::sub-page:horizontal {" + "background:" + _subColor.name(QColor::HexArgb) + ";" + "border-radius:3px;" + "}"; + QString addStyle = "QSlider::add-page:horizontal {" + "background:" + _addColor.name(QColor::HexArgb) + ";" + "border-radius:3px;" + "}"; + _slider->setStyleSheet(grooveStyle + sliderStyle + sliderHoverStyle + sliderPressStyle + subStyle + addStyle); + // Change button color + _decreaseBtn->setColorScheme(_subColor); + _decreaseBtn->setIndicatorColor(QColor(255, 255, 255, 0)); + _increaseBtn->setColorScheme(_subColor); + _increaseBtn->setIndicatorColor(QColor(255, 255, 255, 0)); + } +} diff --git a/FinalProject/slider.h b/FinalProject/slider.h new file mode 100644 index 0000000..fbbf212 --- /dev/null +++ b/FinalProject/slider.h @@ -0,0 +1,72 @@ +#pragma once + +#include +#include +#include +#include + +#include "pushbutton.h" + +class Slider : public QWidget +{ + Q_OBJECT + +public: + Slider(float min, float max, int step, QWidget* parent = 0); + ~Slider(); + +private: + // Settings + float _min; + float _max; + int _step; // step count of slider value + + // State + bool _enabled = true; + + // UI Settings + const QColor _defaultSchemeColor = QColor(58, 143, 183); + QColor _subColor; + QColor _addColor; + QColor _handleColor; + QColor _hoverColor; + QColor _pressColor; + QColor _restoredColor[5]; + + // UI Layout + QHBoxLayout* _mainLayout; + QSlider* _slider; + PushButton* _decreaseBtn; + QLabel* _decreaseIcon; + PushButton* _increaseBtn; + QLabel* _increaseIcon; + + // Transformation function + std::function _transformFunc; // Transform the slider value to actual value + std::function _inversionFunc; // Transform the actual value to slider value + +private: + void generateColor(QColor schemeColor); + +public: + // Getter APIs + QHBoxLayout* mainLayout() const { return _mainLayout; } + float val() const { return _transformFunc(_slider->value()); } + float lev() const { return _slider->value(); } + + // Setter APIs + void setColorScheme(QColor color); + void setMin(float min); + void setMax(float max); + void setStep(float max); + void setValue(float val); // Set the actual value + void setTransformation(std::function transform, std::function inverse); + + void setEnabled(bool enabled = true); + +signals: + void onChanged(float newVal); // Triggers only when user changes the value + void onSetValue(float newVal); // Triggers when user changes the value or setValue() is called + void onDragStart(); + void onDragEnd(); +};