feat: add Preferences dialog for managing theme, rendering, and unit settings

This commit is contained in:
AndreaRigoni
2026-04-10 18:12:05 +00:00
parent e8c10daf6d
commit f8f92ebf3d
8 changed files with 213 additions and 35 deletions

View File

@@ -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

View File

@@ -18,6 +18,8 @@
#include <QFileInfo>
#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<uLib::Vtk::QViewport*>();
for (auto* vp : viewports) {
vp->SetThrottledRendering(throttled);
vp->Render();
}
}
}
MainPanel::~MainPanel() {}

View File

@@ -30,8 +30,7 @@ private slots:
void onSaveAs();
void onExit();
void onDarkTheme();
void onBrightTheme();
void onPreferences();
void onCreateObject(const std::string& className);

View File

@@ -0,0 +1,99 @@
#include "PreferencesDialog.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QFormLayout>
#include <QPushButton>
#include <QLabel>
#include <QGroupBox>
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

View File

@@ -0,0 +1,31 @@
#ifndef GCOMPOSE_PREFERENCESDIALOG_H
#define GCOMPOSE_PREFERENCESDIALOG_H
#include <QDialog>
#include <QCheckBox>
#include <QComboBox>
#include <map>
#include <string>
#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<Settings::Dimension, QComboBox*> m_unitCombos;
};
} // namespace Qt
} // namespace uLib
#endif

View File

@@ -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<Dimension, std::string> m_PreferredUnits;
bool m_ThrottledRendering;
Theme m_Theme;
};
} // namespace Qt

View File

@@ -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()
{

View File

@@ -4,6 +4,7 @@
#include <QWidget>
#include <QVTKOpenGLNativeWidget.h>
#include <QPushButton>
#include <QTimer>
#include <vtkCornerAnnotation.h>
#include <vtkOrientationMarkerWidget.h>
@@ -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