From f8f92ebf3d91ed1da6e6d9c22ff2de34d247c723 Mon Sep 17 00:00:00 2001 From: AndreaRigoni Date: Fri, 10 Apr 2026 18:12:05 +0000 Subject: [PATCH] feat: add Preferences dialog for managing theme, rendering, and unit settings --- app/gcompose/CMakeLists.txt | 2 + app/gcompose/src/MainPanel.cpp | 33 +++++---- app/gcompose/src/MainPanel.h | 3 +- app/gcompose/src/PreferencesDialog.cpp | 99 ++++++++++++++++++++++++++ app/gcompose/src/PreferencesDialog.h | 31 ++++++++ app/gcompose/src/Settings.h | 15 +++- src/Vtk/vtkQViewport.cpp | 28 ++++++-- src/Vtk/vtkQViewport.h | 37 ++++++---- 8 files changed, 213 insertions(+), 35 deletions(-) create mode 100644 app/gcompose/src/PreferencesDialog.cpp create mode 100644 app/gcompose/src/PreferencesDialog.h diff --git a/app/gcompose/CMakeLists.txt b/app/gcompose/CMakeLists.txt index 7949b78..9615d52 100644 --- a/app/gcompose/CMakeLists.txt +++ b/app/gcompose/CMakeLists.txt @@ -17,6 +17,8 @@ add_executable(gcompose src/PropertyWidgets.cpp src/PropertiesPanel.h src/PropertiesPanel.cpp + src/PreferencesDialog.h + src/PreferencesDialog.cpp ) set_target_properties(gcompose PROPERTIES diff --git a/app/gcompose/src/MainPanel.cpp b/app/gcompose/src/MainPanel.cpp index b2a55e6..a5a0431 100644 --- a/app/gcompose/src/MainPanel.cpp +++ b/app/gcompose/src/MainPanel.cpp @@ -18,6 +18,8 @@ #include #include "StyleManager.h" #include "Math/VoxImage.h" +#include "PreferencesDialog.h" +#include "Settings.h" MainPanel::MainPanel(QWidget* parent) : QWidget(parent), m_context(nullptr), m_mainVtkContext(nullptr) { this->setObjectName("MainPanel"); @@ -45,17 +47,12 @@ MainPanel::MainPanel(QWidget* parent) : QWidget(parent), m_context(nullptr), m_m fileMenu->addAction("Open", this, &MainPanel::onOpen); fileMenu->addAction("Save", this, &MainPanel::onSave); fileMenu->addAction("Save As", this, &MainPanel::onSaveAs); + fileMenu->addSeparator(); + fileMenu->addAction("Preferences", this, &MainPanel::onPreferences); + fileMenu->addSeparator(); fileMenu->addAction("Exit", this, &MainPanel::onExit); btnFile->setMenu(fileMenu); - // Theme Menu Button - auto* btnTheme = new QPushButton("Theme", menuPanel); - btnTheme->setObjectName("MenuButton"); - auto* themeMenu = new QMenu(btnTheme); - themeMenu->addAction("Dark", this, &MainPanel::onDarkTheme); - themeMenu->addAction("Bright", this, &MainPanel::onBrightTheme); - btnTheme->setMenu(themeMenu); - // New Menu Button auto* btnNew = new QPushButton("Add", menuPanel); btnNew->setObjectName("MenuButton"); @@ -73,7 +70,6 @@ MainPanel::MainPanel(QWidget* parent) : QWidget(parent), m_context(nullptr), m_m menuLayout->addWidget(logo); menuLayout->addWidget(btnFile); menuLayout->addWidget(btnNew); - menuLayout->addWidget(btnTheme); menuLayout->addStretch(); mainLayout->addWidget(menuPanel); @@ -236,12 +232,21 @@ void MainPanel::onExit() { qApp->quit(); } -void MainPanel::onDarkTheme() { - StyleManager::applyStyle(qApp, "dark"); -} +void MainPanel::onPreferences() { + uLib::Qt::PreferencesDialog dlg(this); + if (dlg.exec() == QDialog::Accepted) { + // Apply theme + auto theme = uLib::Qt::Settings::Instance().GetTheme(); + StyleManager::applyStyle(qApp, theme == uLib::Qt::Settings::Dark ? "dark" : "bright"); -void MainPanel::onBrightTheme() { - StyleManager::applyStyle(qApp, "bright"); + // Apply rendering preference to all viewports + bool throttled = uLib::Qt::Settings::Instance().GetThrottledRendering(); + auto viewports = this->findChildren(); + for (auto* vp : viewports) { + vp->SetThrottledRendering(throttled); + vp->Render(); + } + } } MainPanel::~MainPanel() {} diff --git a/app/gcompose/src/MainPanel.h b/app/gcompose/src/MainPanel.h index a112468..0ad6c54 100644 --- a/app/gcompose/src/MainPanel.h +++ b/app/gcompose/src/MainPanel.h @@ -30,8 +30,7 @@ private slots: void onSaveAs(); void onExit(); - void onDarkTheme(); - void onBrightTheme(); + void onPreferences(); void onCreateObject(const std::string& className); diff --git a/app/gcompose/src/PreferencesDialog.cpp b/app/gcompose/src/PreferencesDialog.cpp new file mode 100644 index 0000000..74a2501 --- /dev/null +++ b/app/gcompose/src/PreferencesDialog.cpp @@ -0,0 +1,99 @@ +#include "PreferencesDialog.h" +#include +#include +#include +#include +#include +#include + +namespace uLib { +namespace Qt { + +PreferencesDialog::PreferencesDialog(QWidget* parent) : QDialog(parent) { + setWindowTitle("Preferences"); + setMinimumWidth(400); + + auto* mainLayout = new QVBoxLayout(this); + mainLayout->setSpacing(20); + mainLayout->setContentsMargins(20, 20, 20, 20); + + // ── General / Rendering Settings ──────────────────────────────────────── + auto* renderingGroup = new QGroupBox("Appearance & Performance", this); + auto* renderingLayout = new QVBoxLayout(renderingGroup); + + auto* themeLayout = new QHBoxLayout(); + themeLayout->addWidget(new QLabel("Color Theme:")); + m_themeCombo = new QComboBox(renderingGroup); + m_themeCombo->addItem("Dark"); + m_themeCombo->addItem("Bright"); + m_themeCombo->setCurrentIndex(Settings::Instance().GetTheme() == Settings::Dark ? 0 : 1); + themeLayout->addWidget(m_themeCombo); + themeLayout->addStretch(); + + renderingLayout->addLayout(themeLayout); + renderingLayout->addSpacing(10); + + m_throttledRendering = new QCheckBox("Enable throttled rendering (recommended for performance)", renderingGroup); + m_throttledRendering->setChecked(Settings::Instance().GetThrottledRendering()); + m_throttledRendering->setToolTip("Limits framerate to ~60fps to reduce CPU/GPU usage."); + + renderingLayout->addWidget(m_throttledRendering); + mainLayout->addWidget(renderingGroup); + + // ── Units Settings ────────────────────────────────────────────────────── + auto* unitsGroup = new QGroupBox("Preferred Units", this); + auto* unitsLayout = new QFormLayout(unitsGroup); + unitsLayout->setLabelAlignment(::Qt::AlignRight); + unitsLayout->setSpacing(10); + + auto addUnitRow = [&](const QString& label, Settings::Dimension dim, const QStringList& units) { + auto* combo = new QComboBox(unitsGroup); + combo->addItems(units); + std::string current = Settings::Instance().GetPreferredUnit(dim); + int idx = combo->findText(QString::fromStdString(current)); + if (idx >= 0) combo->setCurrentIndex(idx); + + unitsLayout->addRow(label + ":", combo); + m_unitCombos[dim] = combo; + }; + + addUnitRow("Length", Settings::Length, {"m", "cm", "mm", "um", "nm"}); + addUnitRow("Angle", Settings::Angle, {"deg", "rad"}); + addUnitRow("Energy", Settings::Energy, {"MeV", "GeV", "eV", "keV", "TeV"}); + addUnitRow("Time", Settings::Time, {"ns", "s", "ms", "us"}); + + mainLayout->addWidget(unitsGroup); + + mainLayout->addStretch(); + + // ── Buttons ───────────────────────────────────────────────────────────── + auto* buttonLayout = new QHBoxLayout(); + buttonLayout->addStretch(); + + auto* btnCancel = new QPushButton("Cancel", this); + connect(btnCancel, &QPushButton::clicked, this, &QDialog::reject); + + auto* btnOk = new QPushButton("Apply", this); + btnOk->setDefault(true); + btnOk->setObjectName("DisplayToggleBtn"); // Reusing high-contrast style + btnOk->setMinimumWidth(100); + connect(btnOk, &QPushButton::clicked, this, &PreferencesDialog::onAccept); + + buttonLayout->addWidget(btnCancel); + buttonLayout->addWidget(btnOk); + mainLayout->addLayout(buttonLayout); +} + +void PreferencesDialog::onAccept() { + Settings::Instance().SetThrottledRendering(m_throttledRendering->isChecked()); + Settings::Instance().SetTheme(m_themeCombo->currentIndex() == 0 ? Settings::Dark : Settings::Bright); + + for (auto const& pair : m_unitCombos) { + Settings::Instance().SetPreferredUnit(pair.first, pair.second->currentText().toStdString()); + } + + accept(); +} + +} // namespace Qt +} // namespace uLib diff --git a/app/gcompose/src/PreferencesDialog.h b/app/gcompose/src/PreferencesDialog.h new file mode 100644 index 0000000..47878ee --- /dev/null +++ b/app/gcompose/src/PreferencesDialog.h @@ -0,0 +1,31 @@ +#ifndef GCOMPOSE_PREFERENCESDIALOG_H +#define GCOMPOSE_PREFERENCESDIALOG_H + +#include +#include +#include +#include +#include +#include "Settings.h" + +namespace uLib { +namespace Qt { + +class PreferencesDialog : public QDialog { + Q_OBJECT +public: + explicit PreferencesDialog(QWidget* parent = nullptr); + +private slots: + void onAccept(); + +private: + QCheckBox* m_throttledRendering; + QComboBox* m_themeCombo; + std::map m_unitCombos; +}; + +} // namespace Qt +} // namespace uLib + +#endif diff --git a/app/gcompose/src/Settings.h b/app/gcompose/src/Settings.h index a867e03..409e1ba 100644 --- a/app/gcompose/src/Settings.h +++ b/app/gcompose/src/Settings.h @@ -23,6 +23,11 @@ public: Dimensionless }; + enum Theme { + Dark, + Bright + }; + void SetPreferredUnit(Dimension dim, const std::string& unit) { m_PreferredUnits[dim] = unit; } @@ -64,9 +69,17 @@ public: return Dimensionless; } + bool GetThrottledRendering() const { return m_ThrottledRendering; } + void SetThrottledRendering(bool enabled) { m_ThrottledRendering = enabled; } + + Theme GetTheme() const { return m_Theme; } + void SetTheme(Theme theme) { m_Theme = theme; } + private: - Settings() {} + Settings() : m_ThrottledRendering(true), m_Theme(Dark) {} std::map m_PreferredUnits; + bool m_ThrottledRendering; + Theme m_Theme; }; } // namespace Qt diff --git a/src/Vtk/vtkQViewport.cpp b/src/Vtk/vtkQViewport.cpp index 3b2839e..83f937a 100644 --- a/src/Vtk/vtkQViewport.cpp +++ b/src/Vtk/vtkQViewport.cpp @@ -20,17 +20,25 @@ QViewport::QViewport(QWidget* parent) , m_VtkWidget(nullptr) , m_GridButton(nullptr) , m_ProjButton(nullptr) + , m_renderTimer(nullptr) + , m_renderPending(false) { // Build the layout – zero margins so VTK fills the entire widget auto* layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); - + m_VtkWidget = new QVTKOpenGLNativeWidget(this); layout->addWidget(m_VtkWidget); - + + // Initialize render timer + m_renderTimer = new QTimer(this); + m_renderTimer->setSingleShot(true); + connect(m_renderTimer, &QTimer::timeout, this, &QViewport::doRender); + // Grid Toggle Button m_GridButton = new QPushButton(m_VtkWidget); + m_GridButton->setText("#"); m_GridButton->setFixedSize(40, 40); m_GridButton->setToolTip("Toggle Grid"); @@ -112,9 +120,21 @@ void QViewport::SetupPipeline() void QViewport::Render() { - if (m_VtkWidget && m_VtkWidget->renderWindow()) - m_VtkWidget->renderWindow()->Render(); + if (!m_throttledRendering) { + doRender(); + return; + } + if (m_renderPending) return; + m_renderPending = true; + m_renderTimer->start(16); } + + void QViewport::doRender() + { + m_renderPending = false; + if (m_VtkWidget && m_VtkWidget->renderWindow()) + m_VtkWidget->renderWindow()->Render(); + } vtkRenderWindow* QViewport::GetRenderWindow() { diff --git a/src/Vtk/vtkQViewport.h b/src/Vtk/vtkQViewport.h index 7bdfa48..a4b9e29 100644 --- a/src/Vtk/vtkQViewport.h +++ b/src/Vtk/vtkQViewport.h @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -39,27 +40,35 @@ public: // Render scene virtual void Render() override; + void SetThrottledRendering(bool enabled) { m_throttledRendering = enabled; } + bool GetThrottledRendering() const { return m_throttledRendering; } + // Direct access to VTK internals virtual vtkRenderWindow* GetRenderWindow() override; virtual vtkRenderWindowInteractor* GetInteractor() override; QVTKOpenGLNativeWidget* GetWidget() { return m_VtkWidget; } virtual void OnSelectionChanged(Prop3D* p) override; + + protected: + virtual void resizeEvent(QResizeEvent* event) override; + + private slots: + void onGridButtonClicked(); + void onProjButtonClicked(); + void doRender(); + + private: + void SetupPipeline(); + + QVTKOpenGLNativeWidget* m_VtkWidget; + QPushButton* m_GridButton; + QPushButton* m_ProjButton; + QTimer* m_renderTimer; + bool m_renderPending = false; + bool m_throttledRendering = true; + }; -protected: - virtual void resizeEvent(QResizeEvent* event) override; - -private slots: - void onGridButtonClicked(); - void onProjButtonClicked(); - -private: - void SetupPipeline(); - - QVTKOpenGLNativeWidget* m_VtkWidget; - QPushButton* m_GridButton; - QPushButton* m_ProjButton; -}; } // namespace Vtk } // namespace uLib