From 08e945945b7990644987722c21dcaa45b463ca8b Mon Sep 17 00:00:00 2001 From: Evan Black Date: Thu, 25 May 2023 19:03:24 -0400 Subject: [PATCH] Allow setting clear color See: https://github.com/usnistgov/NetSimulyzer/issues/48 --- src/settings/SettingsManager.cpp | 36 + src/settings/SettingsManager.h | 90 ++- src/window/MainWindow.cpp | 2 + src/window/scene/SceneWidget.cpp | 10 +- src/window/scene/SceneWidget.h | 14 + src/window/settings/SettingsDialog.cpp | 59 ++ src/window/settings/SettingsDialog.h | 29 + src/window/settings/SettingsDialog.ui | 928 +++++++++++++------------ 8 files changed, 714 insertions(+), 454 deletions(-) diff --git a/src/settings/SettingsManager.cpp b/src/settings/SettingsManager.cpp index 91d1573..0e1a61f 100644 --- a/src/settings/SettingsManager.cpp +++ b/src/settings/SettingsManager.cpp @@ -112,6 +112,27 @@ SettingsManager::WindowTheme SettingsManager::WindowThemeFromInt(int value) { return WindowTheme::Dark; } +SettingsManager::BackgroundColor SettingsManager::BackgroundColorFromInt(int value) { + using BackgroundColor = SettingsManager::BackgroundColor; + + switch (value) { + case static_cast(BackgroundColor::Black): + return BackgroundColor::Black; + case static_cast(BackgroundColor::White): + return BackgroundColor::White; + case static_cast(BackgroundColor::Custom): + return BackgroundColor::Custom; + default: + QMessageBox::critical(nullptr, "Invalid value provided for 'BackgroundColor'!", + "An unrecognised value for 'BackgroundColor': " + QString::number(value) + " was provided"); + QApplication::exit(1); + break; + } + + // Should never happen, but just in case + return BackgroundColor::Black; +} + const SettingsManager::SettingValue &SettingsManager::getQtKey(SettingsManager::Key key) const { auto iterator = SettingsManager::qtKeyMap.find(key); if (iterator == SettingsManager::qtKeyMap.end()) { @@ -171,4 +192,19 @@ void SettingsManager::setTheme(SettingsManager::WindowTheme theme) { } } +QColor SettingsManager::getRenderBackgroundColor() const { + switch (get(Key::RenderBackgroundColor).value()) { + case BackgroundColor::Black: + return QColorConstants::Black; + case BackgroundColor::White: + return QColorConstants::White; + case BackgroundColor::Custom: + return get(Key::RenderBackgroundColorCustom).value(); + } + + // Just in case + std::cerr << "Error getting render background color\n"; + return QColorConstants::Black; +} + } // namespace netsimulyzer diff --git a/src/settings/SettingsManager.h b/src/settings/SettingsManager.h index 2a1f6a0..55f7cc5 100644 --- a/src/settings/SettingsManager.h +++ b/src/settings/SettingsManager.h @@ -1,6 +1,7 @@ #pragma once #include "parser/model.h" +#include #include #include #include @@ -52,6 +53,8 @@ class SettingsManager { RenderMotionTrailLength, RenderLabels, RenderSkybox, + RenderBackgroundColor, + RenderBackgroundColorCustom, ChartDropdownSortOrder, WindowTheme }; @@ -62,6 +65,7 @@ class SettingsManager { enum class MotionTrailRenderMode : int { Always, EnabledOnly, Never }; enum class TimeUnit : int { Milliseconds, Microseconds, Nanoseconds }; enum class WindowTheme : int { Dark, Light, Native }; + enum class BackgroundColor : int { Black, White, Custom }; /** * Convert an int to a `BuildingRenderMode` enum value. @@ -135,6 +139,18 @@ class SettingsManager { */ static WindowTheme WindowThemeFromInt(int value); + /** + * Convert an int to a `BackgroundColor` enum value. + * Necessary since Qt will only allow sending registered types with signals/slots. + * + * @param value + * An integer that corresponds to an enum value + * + * @return + * The enum value corresponding to `value` + */ + static BackgroundColor BackgroundColorFromInt(int value); + /** * Defines when retrieving a value fails, * should it's default value be provided in the return @@ -184,6 +200,8 @@ class SettingsManager { {Key::RenderGrid, {"renderer/showGrid", true}}, {Key::RenderGridStep, {"renderer/gridStepSize", 1}}, {Key::RenderSkybox, {"renderer/enableSkybox", true}}, + {Key::RenderBackgroundColor, {"renderer/backgroundColor", "black"}}, + {Key::RenderBackgroundColorCustom, {"renderer/backgroundColorCustom", QColorConstants::Black}}, {Key::RenderLabels, {"renderer/showLabels", "enabledOnly"}}, {Key::RenderMotionTrails, {"renderer/showMotionTrails", "enabledOnly"}}, {Key::RenderMotionTrailLength, {"renderer/motionTrailLength", 100}}, @@ -349,6 +367,9 @@ class SettingsManager { * The theme to load into the application. */ void setTheme(WindowTheme theme); + + [[nodiscard]] + QColor getRenderBackgroundColor() const; }; // Specialize so we don't have to convert to/from QString all the time @@ -449,6 +470,18 @@ template <> return SettingsManager::WindowTheme::Dark; } +template <> +[[nodiscard]] inline SettingsManager::BackgroundColor SettingsManager::getDefault(SettingsManager::Key key) const { + const auto &settingKey = getQtKey(key); + if (!settingKey.defaultValue.isValid()) { + std::cerr << "Requested default for key: " << settingKey.key.toStdString() << " which has no default\n"; + std::abort(); + } + + // TODO: Use the map value + return SettingsManager::BackgroundColor::Black; +} + template <> [[nodiscard]] inline std::optional SettingsManager::get(Key key, RetrieveMode mode) const { @@ -479,7 +512,8 @@ template <> } template <> -[[nodiscard]] inline std::optional SettingsManager::get(Key key, RetrieveMode mode) const { +[[nodiscard]] +inline std::optional SettingsManager::get(Key key, RetrieveMode mode) const { const auto &settingKey = getQtKey(key); const auto qtSetting = qtSettings.value(settingKey.key); @@ -602,8 +636,8 @@ SettingsManager::get(Key key, RetrieveMode mode) const { } template <> -[[nodiscard]] inline std::optional -SettingsManager::get(Key key, RetrieveMode mode) const { +[[nodiscard]] +inline std::optional SettingsManager::get(Key key, RetrieveMode mode) const { const auto &settingKey = getQtKey(key); const auto qtSetting = qtSettings.value(settingKey.key); @@ -632,6 +666,37 @@ SettingsManager::get(Key key, RetrieveMode mode) const { return {}; } +template <> +[[nodiscard]] inline std::optional SettingsManager::get(Key key, + RetrieveMode mode) const { + const auto &settingKey = getQtKey(key); + const auto qtSetting = qtSettings.value(settingKey.key); + + QString stringMode; + + if (qtSetting.isValid() && !qtSetting.isNull() && qtSetting.template canConvert()) + stringMode = qtSetting.toString(); + else if (mode == RetrieveMode::AllowDefault) + stringMode = settingKey.defaultValue.toString(); + else + return {}; + + if (stringMode == "black") + return {BackgroundColor::Black}; + else if (stringMode == "white") + return {BackgroundColor::White}; + else if (stringMode == "custom") + return {BackgroundColor::Custom}; + else + std::cerr << "Unrecognised 'BackgroundColor' provided '" << stringMode.toStdString() << "' value ignored!\n"; + + // Final catch if the provided string value is invalid + if (mode == RetrieveMode::AllowDefault) + return getDefault(Key::RenderBackgroundColor); + + return {}; +} + template <> inline void SettingsManager::set(SettingsManager::Key key, const SettingsManager::BuildingRenderMode &value) { const auto &settingKey = getQtKey(key); @@ -746,4 +811,23 @@ inline void SettingsManager::set(SettingsManager::Key key, const SettingsManager } } +template <> +inline void SettingsManager::set(SettingsManager::Key key, const SettingsManager::BackgroundColor &value) { + const auto &settingKey = getQtKey(key); + + switch (value) { + case BackgroundColor::Black: + qtSettings.setValue(settingKey.key, "black"); + break; + case BackgroundColor::White: + qtSettings.setValue(settingKey.key, "white"); + break; + case BackgroundColor::Custom: + qtSettings.setValue(settingKey.key, "custom"); + break; + default: + std::cerr << "Unrecognised 'BackgroundColor': " << static_cast(value) << " value not saved!\n"; + } +} + } // namespace netsimulyzer diff --git a/src/window/MainWindow.cpp b/src/window/MainWindow.cpp index e1ab964..c307599 100644 --- a/src/window/MainWindow.cpp +++ b/src/window/MainWindow.cpp @@ -214,6 +214,8 @@ MainWindow::MainWindow() : QMainWindow() { scene.setSkyboxRenderState(enable); }); + QObject::connect(&settingsDialog, &SettingsDialog::backgroundColorChanged, &scene, &SceneWidget::setClearColor); + QObject::connect(&settingsDialog, &SettingsDialog::buildingRenderModeChanged, [this](int mode) { scene.setBuildingRenderMode(SettingsManager::BuildingRenderModeFromInt(mode)); }); diff --git a/src/window/scene/SceneWidget.cpp b/src/window/scene/SceneWidget.cpp index 67a9149..229ebb2 100644 --- a/src/window/scene/SceneWidget.cpp +++ b/src/window/scene/SceneWidget.cpp @@ -261,7 +261,7 @@ void SceneWidget::paintGL() { camera.move(static_cast(frameTimer.elapsed())); renderer.use(camera); - glClearColor(1.0f, 1.0f, 1.0f, 1.0f); + glClearColor(clearColorGl[0], clearColorGl[1], clearColorGl[2], 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); if (renderSkybox) renderer.render(*skyBox); @@ -723,6 +723,14 @@ void SceneWidget::setSkyboxRenderState(bool enable) { renderSkybox = enable; } +void SceneWidget::setClearColor(const QColor &value) { + clearColor = value; + + clearColorGl[0] = static_cast(clearColor.redF()); + clearColorGl[1] = static_cast(clearColor.greenF()); + clearColorGl[2] = static_cast(clearColor.blueF()); +} + void SceneWidget::setBuildingRenderMode(SettingsManager::BuildingRenderMode mode) { buildingRenderMode = mode; } diff --git a/src/window/scene/SceneWidget.h b/src/window/scene/SceneWidget.h index bdb226e..b5dff4d 100644 --- a/src/window/scene/SceneWidget.h +++ b/src/window/scene/SceneWidget.h @@ -64,6 +64,7 @@ #include #include #include +#include #include #include #include @@ -99,6 +100,11 @@ class SceneWidget : public QOpenGLWidget, protected QOpenGLFunctions_3_3_Core { bool renderBuildingOutlines = settings.get(SettingsManager::Key::RenderBuildingOutlines).value(); SettingsManager::MotionTrailRenderMode renderMotionTrails = settings.get(SettingsManager::Key::RenderMotionTrails).value(); + QColor clearColor = settings.getRenderBackgroundColor(); + // Store the converted form of the clear color, + // that OpenGL can accept + std::array clearColorGl{static_cast(clearColor.redF()), static_cast(clearColor.greenF()), + static_cast(clearColor.blueF())}; std::unique_ptr pickingFbo; @@ -233,6 +239,14 @@ class SceneWidget : public QOpenGLWidget, protected QOpenGLFunctions_3_3_Core { */ void setSkyboxRenderState(bool enable); + /** + * + * @param value + * The value to use when clearing the scene. + * Only visible when the skybox is off + */ + void setClearColor(const QColor &value); + /** * Sets the building render behavior * @param mode diff --git a/src/window/settings/SettingsDialog.cpp b/src/window/settings/SettingsDialog.cpp index b626b5b..a5a737a 100644 --- a/src/window/settings/SettingsDialog.cpp +++ b/src/window/settings/SettingsDialog.cpp @@ -4,6 +4,8 @@ #include "src/window/util/file-operations.h" #include "ui_SettingsDialog.h" #include +#include +#include #include #include #include @@ -37,6 +39,12 @@ void SettingsDialog::loadSettings() { ui.checkBoxSkybox->setChecked(settings.get(Key::RenderSkybox).value()); + const auto backgroundColor = *settings.get(Key::RenderBackgroundColor); + ui.comboBackgroundColor->setCurrentIndex(static_cast(backgroundColor)); + + customBackgroundColor = settings.get(SettingsManager::Key::RenderBackgroundColorCustom).value(); + ui.comboBackgroundColor->setItemData(2, customBackgroundColor, Qt::DecorationRole); + const auto buildingMode = settings.get(Key::RenderBuildingMode).value(); ui.comboBuildingRender->setCurrentIndex(ui.comboBuildingRender->findData(static_cast(buildingMode))); @@ -92,9 +100,27 @@ SettingsDialog::SettingsDialog(QWidget *parent) : QDialog(parent) { ui.comboSamples->addItem("8", 8); ui.comboSamples->addItem("16", 16); + ui.comboBackgroundColor->addItem("Black", static_cast(SettingsManager::BackgroundColor::Black)); + ui.comboBackgroundColor->setItemData(0, QColorConstants::Black, Qt::DecorationRole); + + ui.comboBackgroundColor->addItem("White", static_cast(SettingsManager::BackgroundColor::White)); + ui.comboBackgroundColor->setItemData(1, QColorConstants::White, Qt::DecorationRole); + + ui.comboBackgroundColor->addItem("Custom", static_cast(SettingsManager::BackgroundColor::Custom)); + ui.comboBackgroundColor->setItemData(2, customBackgroundColor, Qt::DecorationRole); + ui.comboBuildingRender->addItem("Transparent", static_cast(SettingsManager::BuildingRenderMode::Transparent)); ui.comboBuildingRender->addItem("Opaque", static_cast(SettingsManager::BuildingRenderMode::Opaque)); + QObject::connect(ui.buttonSetCustomBackgroundColor, &QPushButton::clicked, [this]() { + const auto userColor = QColorDialog::getColor(customBackgroundColor, this, "Select a Background Color"); + if (!userColor.isValid()) + return; + + customBackgroundColor = userColor; + ui.comboBackgroundColor->setItemData(2, customBackgroundColor, Qt::DecorationRole); + }); + using SortOrder = SettingsManager::ChartDropdownSortOrder; ui.comboSortOrder->addItem("Alphabetical", static_cast(SortOrder::Alphabetical)); ui.comboSortOrder->addItem("Type", static_cast(SortOrder::Type)); @@ -156,6 +182,7 @@ SettingsDialog::SettingsDialog(QWidget *parent) : QDialog(parent) { QObject::connect(ui.buttonResetSortOrder, &QPushButton::clicked, this, &SettingsDialog::defaultChartSortOrder); QObject::connect(ui.buttonResetSkybox, &QPushButton::clicked, this, &SettingsDialog::defaultEnableSkybox); + QObject::connect(ui.buttonResetBackgroundColor, &QPushButton::clicked, this, &SettingsDialog::defaultBackgroundColor); QObject::connect(ui.buttonResetSamples, &QPushButton::clicked, this, &SettingsDialog::defaultSamples); QObject::connect(ui.buttonResetBuildingRender, &QPushButton::clicked, this, &SettingsDialog::defaultBuildingEffect); QObject::connect(ui.buttonResetBuildingOutlines, &QPushButton::clicked, this, @@ -217,6 +244,7 @@ void SettingsDialog::dialogueButtonClicked(QAbstractButton *button) { ui.buttonResetSortOrder->click(); ui.buttonResetSkybox->click(); + ui.buttonResetBackgroundColor->click(); ui.buttonResetSamples->click(); ui.buttonResetBuildingRender->click(); ui.buttonResetBuildingOutlines->click(); @@ -325,6 +353,31 @@ void SettingsDialog::dialogueButtonClicked(QAbstractButton *button) { emit renderSkyboxChanged(enableSkybox); } + using BackgroundColor = SettingsManager::BackgroundColor; + const auto backgroundColorMode = + SettingsManager::BackgroundColorFromInt(ui.comboBackgroundColor->currentData().toInt()); + // Handle basic modes + if (backgroundColorMode != settings.get(Key::RenderBackgroundColor)) { + settings.set(Key::RenderBackgroundColor, backgroundColorMode); + switch (backgroundColorMode) { + case BackgroundColor::Black: + emit backgroundColorChanged(QColorConstants::Black); + case BackgroundColor::White: + emit backgroundColorChanged(QColorConstants::White); + break; + case BackgroundColor::Custom: + // Ignored here + break; + } + } + + // Handle custom color + if (backgroundColorMode == BackgroundColor::Custom && + customBackgroundColor != settings.get(Key::RenderBackgroundColorCustom).value()) { + settings.set(Key::RenderBackgroundColorCustom, customBackgroundColor); + emit backgroundColorChanged(customBackgroundColor); + } + auto buildingRenderMode = SettingsManager::BuildingRenderModeFromInt(ui.comboBuildingRender->currentData().toInt()); if (buildingRenderMode != settings.get(Key::RenderBuildingMode).value()) { settings.set(Key::RenderBuildingMode, buildingRenderMode); @@ -456,6 +509,12 @@ void SettingsDialog::defaultChartSortOrder() { ui.comboSortOrder->setCurrentIndex(ui.comboSortOrder->findData(defaultValue)); } +void SettingsDialog::defaultBackgroundColor() { + const auto defaultValue = static_cast( + settings.getDefault(SettingsManager::Key::RenderBackgroundColor)); + ui.comboBackgroundColor->setCurrentIndex(ui.comboBackgroundColor->findData(defaultValue)); +} + void SettingsDialog::defaultSamples() { ui.comboSamples->setCurrentIndex( ui.comboSamples->findData(settings.getDefault(SettingsManager::Key::NumberSamples))); diff --git a/src/window/settings/SettingsDialog.h b/src/window/settings/SettingsDialog.h index 808de7c..289b428 100644 --- a/src/window/settings/SettingsDialog.h +++ b/src/window/settings/SettingsDialog.h @@ -36,6 +36,7 @@ #include "src/settings/SettingsManager.h" #include "src/window/scene/SceneWidget.h" #include "ui_SettingsDialog.h" +#include #include #include @@ -76,6 +77,12 @@ class SettingsDialog : public QDialog { */ double passedTimeStep = 10.0; + /** + * The user specified clear color. + * Used when the skybox is off + */ + QColor customBackgroundColor = settings.get(SettingsManager::Key::RenderBackgroundColorCustom).value(); + /** * Sets the suffix in the time step preference SpinBox * @@ -127,6 +134,11 @@ class SettingsDialog : public QDialog { */ void defaultSamples(); + /** + * Set the background color combo box to it's default value + */ + void defaultBackgroundColor(); + /** * Set the Skybox checkbox to the default value */ @@ -209,6 +221,13 @@ class SettingsDialog : public QDialog { void setTimeStep(double value); signals: + + // Ignore unimplemented function warning + // for Qt Signals, since those are generated + // For us elsewhere +#pragma clang diagnostic push +#pragma ide diagnostic ignored "NotImplementedFunctions" + /** * Signal emitted when the user saves a new Move Speed. * @@ -325,6 +344,14 @@ class SettingsDialog : public QDialog { */ void renderSkyboxChanged(bool enable); + /** + * Signal emitted when the user changes the background color. + * + * @param value + * The new value to use for the clear color + */ + void backgroundColorChanged(QColor value); + /** * Signal emitted when the user changes the * Building render mode. @@ -404,6 +431,8 @@ class SettingsDialog : public QDialog { * with a trailing slash. */ void resourcePathChanged(const QString &dir); + +#pragma clang diagnostic pop }; } // namespace netsimulyzer diff --git a/src/window/settings/SettingsDialog.ui b/src/window/settings/SettingsDialog.ui index 7d48b90..299522b 100644 --- a/src/window/settings/SettingsDialog.ui +++ b/src/window/settings/SettingsDialog.ui @@ -44,111 +44,61 @@ 0 - 0 - 530 - 1211 + -423 + 531 + 1104 - - - - Play/Pause - - - - - - - - 16 - - - - Charts - - - Qt::AlignCenter - - - - - - - Motion Trail Length - - - - - - - Up - - - - - - - Show Motion Trails - - - - - - - - - - 30 - - - 90 - - - 45 - - - Qt::Horizontal - - - - - - - - - - Browse - - - - - + + Default - - - - Forward - - - - - - - Mouse Turn Speed - - + + - - - - Theme - - + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + @@ -157,52 +107,56 @@ - - + + Default - - + + - Field of View + Default - - - - Sort Order + + + + + 16 + - - - - - Default + Graphics + + + Qt::AlignCenter - - + + + + + - Move Speed + Default - - - - Default + + + + W - - + + - Default + Forward @@ -213,93 +167,68 @@ - - + + - Left + Background Color - - + + - Default + Time Step Preference - - + + - Default - - - - - - - Q + Show Building Outlines - - - - Default + + + + 30 - - - - - - - - - Default + + 90 - - - - - - Samples (MSAA) + + 45 - - - - - - D + + Qt::Horizontal - - + + - SkyBox + Default - - - - X + + + + Default - - + + Default - - - - 0 - + + - + Qt::Horizontal @@ -312,29 +241,14 @@ - - - - 0 - 0 - - - - - 0 - 0 - - - - - + - + Qt::Horizontal @@ -348,182 +262,188 @@ - - + + - Time Step Preference + Default - - - - Turn Left - - + + - - + + - Label Size + Grid Step Size - - + + - Default + Field of View - - + + - - + + + + + 0 + 0 + + + + + 16 + + - Default + Window + + + Qt::AlignCenter - + Default - - - - 1 - - - 1000 - - - 1 + + + + + 0 + 0 + - - 5 + + + 16 + - - Qt::Horizontal + + Camera - - false + + Qt::AlignCenter - - QSlider::NoTicks + + + + + + D - - - - Turn Right + + + + X - - + + Default - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - 0 - 0 - - - - - 0 - 0 - + + + + Turn Right + + + + 16 - Playback + Charts Qt::AlignCenter - - + + - Default + Label Size - - + + + + Q + + + + + - Default + Show Motion Trails - - + + + + Z + + + + + Default - - + + + + true + + + + + + + Sort Order + + + + + + + Show Labels + + + + + + + 0 + - + Qt::Horizontal @@ -536,14 +456,29 @@ - + + + + 0 + 0 + + + + + 0 + 0 + + + + + - + Qt::Horizontal @@ -557,65 +492,49 @@ - - - - 1 - - - 100 - - - 50 + + + + Down - - Qt::Horizontal + + + + + + Right - - + + Default - - + + - Backward + Play/Pause - - - - - 0 - 0 - - - - - 16 - - + + - Camera - - - Qt::AlignCenter + Browse - - + + Default - + @@ -638,173 +557,234 @@ - - - - Z + + + + 1 + + + 1000 + + + 1 + + + 5 + + + Qt::Horizontal + + + false + + + QSlider::NoTicks - - - - S + + + + Default - - + + Default - - + + - Grid Step Size + Default - - - - W + + + + Move Speed - - + + - Resource Directory + Turn Left - - + + - A + S - - + + Default - - - - - + + 20 5 - - 5 - Qt::Horizontal - - - - true - - - - - - - Building Effect - - - - - - - - + + - Show Grid + Mouse Turn Speed - - + + - 10 + 1 - 8192 + 100 - 100 + 50 Qt::Horizontal - - + + Default - - + + Default - - + + - Right + Backward - - + + - Show Labels + Default - - + + + + + + + + 0 + 0 + + + + + 0 + 0 + + 16 - Graphics + Resources Qt::AlignCenter - - + + + + A + + + + + + + SkyBox + + + + + Default - - + + + + Default + + + + + + + Default + + + + + + + Left + + + + + + + 20 + + + 5 + + + 5 + + + Qt::Horizontal + + + + + 0 @@ -823,7 +803,7 @@ - Resources + Playback Qt::AlignCenter @@ -837,68 +817,116 @@ - - + + + + Motion Trail Length + + + + + + + Resource Directory + + + + + + + Samples (MSAA) + + + + + Default - - + + Default - - + + - Show Building Outlines + Up - - + + - Down + Building Effect - - + + - - + + + + Show Grid + + + + + + + + + + 10 + - 20 + 8192 - - 5 + + 100 Qt::Horizontal - - - - - 0 - 0 - - - - - 16 - + + + + Default + + + + - Window + Theme - - Qt::AlignCenter + + + + + + + + + + + Set Custom + + + + + + + + + Default