From 24ec3267151692a36382f279dbeccc41af07bf16 Mon Sep 17 00:00:00 2001 From: AndreaRigoni Date: Wed, 15 Apr 2026 14:50:46 +0000 Subject: [PATCH] feat: implement configurable font settings for VTK viewports and GUI elements with persistent preferences. --- app/gcompose/src/ContextPanel.cpp | 2 + app/gcompose/src/ContextPanel.h | 1 + app/gcompose/src/MainPanel.cpp | 23 +++++-- app/gcompose/src/MainPanel.h | 2 + app/gcompose/src/PreferencesDialog.cpp | 85 ++++++++++++++++++++++++++ app/gcompose/src/PreferencesDialog.h | 18 ++++++ app/gcompose/src/PropertiesPanel.cpp | 4 ++ app/gcompose/src/PropertiesPanel.h | 3 + app/gcompose/src/PropertyWidgets.cpp | 61 +++++++++++++++--- app/gcompose/src/PropertyWidgets.h | 25 +++++++- app/gcompose/src/Settings.h | 16 ++++- app/gcompose/src/StyleManager.cpp | 50 ++++++++++++--- app/gcompose/src/StyleManager.h | 4 +- app/gcompose/src/ViewportPane.cpp | 4 ++ app/gcompose/src/ViewportPane.h | 2 + app/gcompose/src/main.cpp | 6 +- src/Core/FontConfig.h | 48 +++++++++++++++ src/Vtk/vtkViewport.cpp | 43 ++++++++++++- src/Vtk/vtkViewport.h | 8 +++ src/Vtk/vtkViewportProperties.h | 56 +++++++++++++++++ 20 files changed, 433 insertions(+), 28 deletions(-) create mode 100644 src/Core/FontConfig.h create mode 100644 src/Vtk/vtkViewportProperties.h diff --git a/app/gcompose/src/ContextPanel.cpp b/app/gcompose/src/ContextPanel.cpp index f34a986..3d9040b 100644 --- a/app/gcompose/src/ContextPanel.cpp +++ b/app/gcompose/src/ContextPanel.cpp @@ -57,6 +57,8 @@ ContextPanel::ContextPanel(QWidget* parent) m_splitter->setSizes(sizes); m_layout->addWidget(m_splitter); + + connect(m_propertiesPanel, &PropertiesPanel::propertyUpdated, this, &ContextPanel::propertyUpdated); connect(m_treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &ContextPanel::onSelectionChanged); diff --git a/app/gcompose/src/ContextPanel.h b/app/gcompose/src/ContextPanel.h index dd0bca6..0366495 100644 --- a/app/gcompose/src/ContextPanel.h +++ b/app/gcompose/src/ContextPanel.h @@ -25,6 +25,7 @@ public: signals: void objectSelected(uLib::Object* obj); + void propertyUpdated(); private slots: void onSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected); diff --git a/app/gcompose/src/MainPanel.cpp b/app/gcompose/src/MainPanel.cpp index 623360c..8889bc8 100644 --- a/app/gcompose/src/MainPanel.cpp +++ b/app/gcompose/src/MainPanel.cpp @@ -6,6 +6,8 @@ #include "Core/ObjectsContext.h" #include "Vtk/vtkObjectsContext.h" #include "Vtk/vtkQViewport.h" +#include "Vtk/vtkViewportProperties.h" +#include #include #include #include @@ -22,7 +24,7 @@ #include "PreferencesDialog.h" #include "Settings.h" -MainPanel::MainPanel(QWidget* parent) : QWidget(parent), m_context(nullptr), m_mainVtkContext(nullptr) { +MainPanel::MainPanel(QWidget* parent) : QWidget(parent), m_context(nullptr), m_mainVtkContext(nullptr), m_viewportProps(nullptr) { this->setObjectName("MainPanel"); this->setAttribute(Qt::WA_StyledBackground); auto* mainLayout = new QVBoxLayout(this); @@ -98,6 +100,13 @@ MainPanel::MainPanel(QWidget* parent) : QWidget(parent), m_context(nullptr), m_m m_firstPane->setObject(obj); } }); + + connect(m_contextPanel, &ContextPanel::propertyUpdated, [this](){ + auto viewports = this->findChildren(); + for (auto* vp : viewports) { + vp->Render(); + } + }); // Set initial sizes: Context(250), Viewport(600), Properties(250) QList sizes; @@ -244,15 +253,21 @@ void MainPanel::onExit() { void MainPanel::onPreferences() { uLib::Qt::PreferencesDialog dlg(this); if (dlg.exec() == QDialog::Accepted) { - // Apply theme + // Apply theme and GUI font auto theme = uLib::Qt::Settings::Instance().GetTheme(); - StyleManager::applyStyle(qApp, theme == uLib::Qt::Settings::Dark ? "dark" : "bright"); + auto guiFont = uLib::Qt::Settings::Instance().GetGuiFont(); + StyleManager::applyStyle(qApp, theme == uLib::Qt::Settings::Dark ? "dark" : "bright", guiFont); - // Apply rendering preference to all viewports + // Apply rendering and font preferences to all viewports bool throttled = uLib::Qt::Settings::Instance().GetThrottledRendering(); + auto font = uLib::Qt::Settings::Instance().GetFont(); + auto fontColor = uLib::Qt::Settings::Instance().GetFontColor(); + auto viewports = this->findChildren(); for (auto* vp : viewports) { vp->SetThrottledRendering(throttled); + vp->SetFont(font); + vp->SetFontColor(fontColor); vp->Render(); } } diff --git a/app/gcompose/src/MainPanel.h b/app/gcompose/src/MainPanel.h index 0ad6c54..c7fdfab 100644 --- a/app/gcompose/src/MainPanel.h +++ b/app/gcompose/src/MainPanel.h @@ -12,6 +12,7 @@ namespace uLib { class ObjectsContext; namespace Vtk { class ObjectsContext; + class ViewportProperties; } } @@ -40,6 +41,7 @@ private: ContextPanel* m_contextPanel; uLib::ObjectsContext* m_context; uLib::Vtk::ObjectsContext* m_mainVtkContext; + uLib::Vtk::ViewportProperties* m_viewportProps; }; #endif // MAINPANEL_H diff --git a/app/gcompose/src/PreferencesDialog.cpp b/app/gcompose/src/PreferencesDialog.cpp index 74a2501..2eca8c6 100644 --- a/app/gcompose/src/PreferencesDialog.cpp +++ b/app/gcompose/src/PreferencesDialog.cpp @@ -5,6 +5,8 @@ #include #include #include +#include +#include namespace uLib { namespace Qt { @@ -64,6 +66,77 @@ PreferencesDialog::PreferencesDialog(QWidget* parent) : QDialog(parent) { mainLayout->addWidget(unitsGroup); + // ── Font Configuration ────────────────────────────────────────────────── + auto* fontGroup = new QGroupBox("Viewport Font Configuration", this); + auto* fontLayout = new QFormLayout(fontGroup); + fontLayout->setLabelAlignment(::Qt::AlignRight); + + FontConfig currentFont = Settings::Instance().GetFont(); + m_currentFontColor = Settings::Instance().GetFontColor(); + + m_fontFamilies = new QComboBox(fontGroup); + m_fontFamilies->addItems({"Arial", "Courier", "Times"}); + m_fontFamilies->setCurrentText(QString::fromStdString(currentFont.family)); + + m_fontSize = new QSpinBox(fontGroup); + m_fontSize->setRange(6, 72); + m_fontSize->setValue(currentFont.size); + + m_fontBold = new QCheckBox("Bold", fontGroup); + m_fontBold->setChecked(currentFont.bold); + + m_fontItalic = new QCheckBox("Italic", fontGroup); + m_fontItalic->setChecked(currentFont.italic); + + m_fontColorBtn = new QPushButton(fontGroup); + m_fontColorBtn->setFixedWidth(60); + updateFontColorButton(); + connect(m_fontColorBtn, &QPushButton::clicked, [this](){ + QColor c = QColor::fromRgbF(m_currentFontColor.x(), m_currentFontColor.y(), m_currentFontColor.z()); + QColor selected = QColorDialog::getColor(c, this, "Select Font Color"); + if (selected.isValid()) { + m_currentFontColor = Vector3d(selected.redF(), selected.greenF(), selected.blueF()); + updateFontColorButton(); + } + }); + + fontLayout->addRow("Family:", m_fontFamilies); + fontLayout->addRow("Size:", m_fontSize); + fontLayout->addRow(m_fontBold); + fontLayout->addRow(m_fontItalic); + fontLayout->addRow("Color:", m_fontColorBtn); + + mainLayout->addWidget(fontGroup); + + // ── GUI Font Configuration ────────────────────────────────────────────── + auto* guiFontGroup = new QGroupBox("GUI Font Configuration", this); + auto* guiFontLayout = new QFormLayout(guiFontGroup); + guiFontLayout->setLabelAlignment(::Qt::AlignRight); + + FontConfig currentGuiFont = Settings::Instance().GetGuiFont(); + + m_guiFontFamilies = new QComboBox(guiFontGroup); + m_guiFontFamilies->setEditable(true); + m_guiFontFamilies->addItems({"Inter", "Roboto", "Segoe UI", "Arial", "Ubuntu"}); + m_guiFontFamilies->setCurrentText(QString::fromStdString(currentGuiFont.family)); + + m_guiFontSize = new QSpinBox(guiFontGroup); + m_guiFontSize->setRange(6, 48); + m_guiFontSize->setValue(currentGuiFont.size); + + m_guiFontBold = new QCheckBox("Bold", guiFontGroup); + m_guiFontBold->setChecked(currentGuiFont.bold); + + m_guiFontItalic = new QCheckBox("Italic", guiFontGroup); + m_guiFontItalic->setChecked(currentGuiFont.italic); + + guiFontLayout->addRow("Family:", m_guiFontFamilies); + guiFontLayout->addRow("Size:", m_guiFontSize); + guiFontLayout->addRow(m_guiFontBold); + guiFontLayout->addRow(m_guiFontItalic); + + mainLayout->addWidget(guiFontGroup); + mainLayout->addStretch(); // ── Buttons ───────────────────────────────────────────────────────────── @@ -92,8 +165,20 @@ void PreferencesDialog::onAccept() { Settings::Instance().SetPreferredUnit(pair.first, pair.second->currentText().toStdString()); } + FontConfig font(m_fontFamilies->currentText().toStdString(), m_fontSize->value(), m_fontBold->isChecked(), m_fontItalic->isChecked()); + Settings::Instance().SetFont(font); + Settings::Instance().SetFontColor(m_currentFontColor); + + FontConfig guiFont(m_guiFontFamilies->currentText().toStdString(), m_guiFontSize->value(), m_guiFontBold->isChecked(), m_guiFontItalic->isChecked()); + Settings::Instance().SetGuiFont(guiFont); + accept(); } +void PreferencesDialog::updateFontColorButton() { + QColor c = QColor::fromRgbF(m_currentFontColor.x(), m_currentFontColor.y(), m_currentFontColor.z()); + m_fontColorBtn->setStyleSheet(QString("background-color: %1; border: 1px solid #555; height: 18px;").arg(c.name())); +} + } // namespace Qt } // namespace uLib diff --git a/app/gcompose/src/PreferencesDialog.h b/app/gcompose/src/PreferencesDialog.h index 47878ee..747da05 100644 --- a/app/gcompose/src/PreferencesDialog.h +++ b/app/gcompose/src/PreferencesDialog.h @@ -4,6 +4,8 @@ #include #include #include +#include +#include #include #include #include "Settings.h" @@ -23,6 +25,22 @@ private: QCheckBox* m_throttledRendering; QComboBox* m_themeCombo; std::map m_unitCombos; + + // Font Configuration + QComboBox* m_fontFamilies; + QSpinBox* m_fontSize; + QCheckBox* m_fontBold; + QCheckBox* m_fontItalic; + QPushButton* m_fontColorBtn; + Vector3d m_currentFontColor; + + // GUI Font Configuration + QComboBox* m_guiFontFamilies; + QSpinBox* m_guiFontSize; + QCheckBox* m_guiFontBold; + QCheckBox* m_guiFontItalic; + + void updateFontColorButton(); }; } // namespace Qt diff --git a/app/gcompose/src/PropertiesPanel.cpp b/app/gcompose/src/PropertiesPanel.cpp index 4966e63..ab6cddb 100644 --- a/app/gcompose/src/PropertiesPanel.cpp +++ b/app/gcompose/src/PropertiesPanel.cpp @@ -30,6 +30,10 @@ PropertiesPanel::PropertiesPanel(QWidget* parent) : QWidget(parent) { // Editor m_editor = new uLib::Qt::PropertyEditor(this); m_layout->addWidget(m_editor, 1); + + connect(m_editor, &uLib::Qt::PropertyEditor::propertyUpdated, [this](uLib::PropertyBase*){ + emit propertyUpdated(); + }); } void PropertiesPanel::setObject(uLib::Object* obj) { diff --git a/app/gcompose/src/PropertiesPanel.h b/app/gcompose/src/PropertiesPanel.h index cff62aa..f7e6fc0 100644 --- a/app/gcompose/src/PropertiesPanel.h +++ b/app/gcompose/src/PropertiesPanel.h @@ -24,6 +24,9 @@ public: /** @brief Sets the object to be inspected. */ void setObject(uLib::Object* obj); +signals: + void propertyUpdated(); + private: QVBoxLayout* m_layout; QWidget* m_titleBar; diff --git a/app/gcompose/src/PropertyWidgets.cpp b/app/gcompose/src/PropertyWidgets.cpp index 8289f4c..eb162e7 100644 --- a/app/gcompose/src/PropertyWidgets.cpp +++ b/app/gcompose/src/PropertyWidgets.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include "Settings.h" namespace uLib { @@ -151,7 +152,7 @@ DoublePropertyWidget::DoublePropertyWidget(Property* prop, QWidget* pare } m_Edit->setValue(prop->Get()); m_Layout->addWidget(m_Edit, 1); - connect(m_Edit, &UnitLineEdit::valueManualChanged, [this](double val){ m_Prop->Set(val); }); + connect(m_Edit, &UnitLineEdit::valueManualChanged, [this](double val){ m_Prop->Set(val); emit updated(); }); m_Connection = uLib::Object::connect(m_Prop, &Property::Updated, [this](){ m_Edit->setValue(m_Prop->Get()); }); @@ -169,7 +170,7 @@ FloatPropertyWidget::FloatPropertyWidget(Property* prop, QWidget* parent) } m_Edit->setValue(prop->Get()); m_Layout->addWidget(m_Edit, 1); - connect(m_Edit, &UnitLineEdit::valueManualChanged, [this](double val){ m_Prop->Set((float)val); }); + connect(m_Edit, &UnitLineEdit::valueManualChanged, [this](double val){ m_Prop->Set((float)val); emit updated(); }); m_Connection = uLib::Object::connect(m_Prop, &Property::Updated, [this](){ m_Edit->setValue((double)m_Prop->Get()); }); @@ -188,7 +189,7 @@ IntPropertyWidget::IntPropertyWidget(Property* prop, QWidget* parent) } m_Edit->setValue(prop->Get()); m_Layout->addWidget(m_Edit, 1); - connect(m_Edit, &UnitLineEdit::valueManualChanged, [this](double val){ m_Prop->Set((int)val); }); + connect(m_Edit, &UnitLineEdit::valueManualChanged, [this](double val){ m_Prop->Set((int)val); emit updated(); }); m_Connection = uLib::Object::connect(m_Prop, &Property::Updated, [this](){ m_Edit->setValue((double)m_Prop->Get()); }); @@ -199,7 +200,7 @@ BoolPropertyWidget::BoolPropertyWidget(Property* prop, QWidget* parent) m_CheckBox = new QCheckBox(this); m_CheckBox->setChecked(prop->Get()); m_Layout->addWidget(m_CheckBox, 1); - connect(m_CheckBox, &QCheckBox::toggled, [this](bool val){ if (m_Prop->Get() != val) m_Prop->Set(val); }); + connect(m_CheckBox, &QCheckBox::toggled, [this](bool val){ if (m_Prop->Get() != val) { m_Prop->Set(val); emit updated(); } }); m_Connection = uLib::Object::connect(m_Prop, &Property::Updated, [this](){ if (m_CheckBox->isChecked() != m_Prop->Get()) { QSignalBlocker blocker(m_CheckBox); @@ -222,7 +223,7 @@ RangePropertyWidget::RangePropertyWidget(Property* prop, QWidget* parent m_Layout->addWidget(m_Edit, 0); connect(m_Slider, &QSlider::valueChanged, this, &RangePropertyWidget::onSliderChanged); - connect(m_Edit, &UnitLineEdit::valueManualChanged, [this](double val){ m_Prop->Set(val); }); + connect(m_Edit, &UnitLineEdit::valueManualChanged, [this](double val){ m_Prop->Set(val); emit updated(); }); m_Connection = uLib::Object::connect(m_Prop, &Property::Updated, [this](){ this->updateUi(); @@ -244,6 +245,7 @@ void RangePropertyWidget::updateUi() { void RangePropertyWidget::onSliderChanged(int val) { double realVal = m_Prop->GetMin() + (val / 100.0) * (m_Prop->GetMax() - m_Prop->GetMin()); m_Prop->Set(realVal); + emit updated(); } ColorPropertyWidget::ColorPropertyWidget(Property* prop, QWidget* parent) @@ -276,6 +278,7 @@ void ColorPropertyWidget::onClicked() { QColor selected = QColorDialog::getColor(current, this, "Select Color"); if (selected.isValid()) { m_Prop->Set(Vector3d(selected.redF(), selected.greenF(), selected.blueF())); + emit updated(); } } @@ -286,7 +289,7 @@ StringPropertyWidget::StringPropertyWidget(Property* prop, QWidget* m_Layout->addWidget(m_LineEdit, 1); connect(m_LineEdit, &QLineEdit::editingFinished, [this](){ std::string val = m_LineEdit->text().toStdString(); - if (m_Prop->Get() != val) m_Prop->Set(val); + if (m_Prop->Get() != val) { m_Prop->Set(val); emit updated(); } }); m_Connection = uLib::Object::connect(m_Prop, &Property::Updated, [this](){ if (m_LineEdit->text().toStdString() != m_Prop->Get()) { @@ -297,6 +300,40 @@ StringPropertyWidget::StringPropertyWidget(Property* prop, QWidget* } StringPropertyWidget::~StringPropertyWidget() {} +FontPropertyWidget::FontPropertyWidget(Property* prop, QWidget* parent) + : PropertyWidgetBase(prop, parent), m_Prop(prop) { + m_Button = new QPushButton(this); + m_Button->setMinimumWidth(100); + this->updateButtonText(); + m_Layout->addWidget(m_Button, 1); + + connect(m_Button, &QPushButton::clicked, this, &FontPropertyWidget::onClicked); + m_Connection = uLib::Object::connect(m_Prop, &Property::Updated, [this](){ + this->updateButtonText(); + }); +} +FontPropertyWidget::~FontPropertyWidget() {} + +void FontPropertyWidget::updateButtonText() { + FontConfig f = m_Prop->Get(); + m_Button->setText(QString::fromStdString(f.family) + " " + QString::number(f.size)); +} + +void FontPropertyWidget::onClicked() { + FontConfig current = m_Prop->Get(); + QFont font(QString::fromStdString(current.family), current.size); + font.setBold(current.bold); + font.setItalic(current.italic); + + bool ok; + QFont selected = QFontDialog::getFont(&ok, font, this, "Select Font"); + if (ok) { + FontConfig newF(selected.family().toStdString(), selected.pointSize(), selected.bold(), selected.italic()); + m_Prop->Set(newF); + emit updated(); + } +} + class GroupHeaderWidget : public QWidget { public: GroupHeaderWidget(const QString& name, QWidget* parent = nullptr) : QWidget(parent) { @@ -332,8 +369,9 @@ public: // Get initial value if (auto* p = dynamic_cast*>(prop)) { m_Combo->setCurrentIndex(p->Get()); - connect(m_Combo, &QComboBox::currentIndexChanged, [p](int index){ + connect(m_Combo, &QComboBox::currentIndexChanged, [this, p](int index){ p->Set(index); + emit updated(); }); // Store connection in base m_Connection so it's auto-disconnected on destruction. m_Connection = uLib::Object::connect(p, &Property::Updated, [this, p](){ @@ -374,6 +412,9 @@ PropertyEditor::PropertyEditor(QWidget* parent) : QWidget(parent), m_Object(null registerFactory([](PropertyBase* p, QWidget* parent){ return new StringPropertyWidget(static_cast*>(p), parent); }); + registerFactory([](PropertyBase* p, QWidget* parent){ + return new FontPropertyWidget(static_cast*>(p), parent); + }); // Register EnumProperty specifically (needs to check type since it holds Property but is EnumProperty) m_Factories[std::type_index(typeid(EnumProperty))] = [](PropertyBase* p, QWidget* parent) { @@ -462,6 +503,12 @@ void PropertyEditor::setObject(::uLib::Object* obj, bool displayOnly) { } if (widget) { + if (auto* propWidget = qobject_cast(widget)) { + connect(propWidget, &PropertyWidgetBase::updated, [this, prop](){ + emit propertyUpdated(prop); + }); + } + if (!groupName.empty()) { // Indent grouped properties widget->setContentsMargins(16, 0, 0, 0); diff --git a/app/gcompose/src/PropertyWidgets.h b/app/gcompose/src/PropertyWidgets.h index 3eca02f..cfb2c62 100644 --- a/app/gcompose/src/PropertyWidgets.h +++ b/app/gcompose/src/PropertyWidgets.h @@ -17,6 +17,7 @@ class QSlider; #include "Core/Property.h" #include "Core/Object.h" #include "Core/Signal.h" +#include "Core/FontConfig.h" #include "Math/Dense.h" #include "Settings.h" @@ -32,6 +33,9 @@ public: virtual ~PropertyWidgetBase(); PropertyBase* getProperty() const { return m_BaseProperty; } +signals: + void updated(); + protected: PropertyBase* m_BaseProperty; QHBoxLayout* m_Layout; @@ -121,7 +125,10 @@ public: connect(m_Edits[i], &UnitLineEdit::valueManualChanged, [this, i](double val){ VecT v = m_Prop->Get(); v(i) = (typename VecT::Scalar)val; - if (m_Prop->Get() != v) m_Prop->Set(v); + if (m_Prop->Get() != v) { + m_Prop->Set(v); + emit updated(); + } }); } updateEdits(); @@ -191,6 +198,19 @@ private: QLineEdit* m_LineEdit; }; +class FontPropertyWidget : public PropertyWidgetBase { + Q_OBJECT +public: + FontPropertyWidget(Property* prop, QWidget* parent = nullptr); + virtual ~FontPropertyWidget(); +private slots: + void onClicked(); +private: + void updateButtonText(); + Property* m_Prop; + QPushButton* m_Button; +}; + class PropertyEditor : public QWidget { Q_OBJECT public: @@ -202,6 +222,9 @@ public: m_Factories[std::type_index(typeid(T))] = factory; } +signals: + void propertyUpdated(PropertyBase* prop = nullptr); + private: void clear(); uLib::Object* m_Object; diff --git a/app/gcompose/src/Settings.h b/app/gcompose/src/Settings.h index 409e1ba..18b1bc7 100644 --- a/app/gcompose/src/Settings.h +++ b/app/gcompose/src/Settings.h @@ -4,6 +4,8 @@ #include #include #include "Math/Units.h" +#include "Core/FontConfig.h" +#include "Math/Dense.h" namespace uLib { namespace Qt { @@ -75,11 +77,23 @@ public: Theme GetTheme() const { return m_Theme; } void SetTheme(Theme theme) { m_Theme = theme; } + FontConfig GetFont() const { return m_Font; } + void SetFont(const FontConfig& font) { m_Font = font; } + + FontConfig GetGuiFont() const { return m_GuiFont; } + void SetGuiFont(const FontConfig& font) { m_GuiFont = font; } + + Vector3d GetFontColor() const { return m_FontColor; } + void SetFontColor(const Vector3d& color) { m_FontColor = color; } + private: - Settings() : m_ThrottledRendering(true), m_Theme(Dark) {} + Settings() : m_ThrottledRendering(true), m_Theme(Dark), m_Font("Arial", 10), m_GuiFont("Inter", 9), m_FontColor(1.0, 1.0, 1.0) {} std::map m_PreferredUnits; bool m_ThrottledRendering; Theme m_Theme; + FontConfig m_Font; + FontConfig m_GuiFont; + Vector3d m_FontColor; }; } // namespace Qt diff --git a/app/gcompose/src/StyleManager.cpp b/app/gcompose/src/StyleManager.cpp index 3808af8..cdf9797 100644 --- a/app/gcompose/src/StyleManager.cpp +++ b/app/gcompose/src/StyleManager.cpp @@ -1,11 +1,15 @@ #include "StyleManager.h" #include +#include "Core/FontConfig.h" static const QString DARK_THEME = R"( QWidget#MenuPanel { background-color: #2b2b2b; border-bottom: 1px solid #111; } QLabel#LogoLabel { font-weight: bold; color: #0078d7; font-size: 14px; letter-spacing: 1px; } QPushButton#MenuButton { background: transparent; color: #ccc; border: none; padding: 5px 10px; } QPushButton#MenuButton:hover { background: #3c3c3c; color: white; border-radius: 4px; } +QPushButton { background-color: #3e3e42; color: white; border: 1px solid #555; padding: 4px 12px; border-radius: 2px; } +QPushButton:hover { background-color: #505050; border-color: #0078d7; } +QPushButton:pressed { background-color: #0078d7; } QWidget#PaneTitleBar { background-color: #333; color: white; border-bottom: 2px solid #222; } QLabel#TitleLabel { font-weight: bold; margin-left: 2px; } QToolButton#PaneCloseButton { border: none; font-weight: bold; background: transparent; color: #ccc; } @@ -23,8 +27,11 @@ QScrollArea > QWidget > QWidget { background: transparent; } /* Property Widgets Styling */ QLabel { color: #cccccc; } -QDoubleSpinBox, QSpinBox, QLineEdit { background: #3c3c3c; color: #f1f1f1; border: 1px solid #3e3e42; padding: 2px 4px; border-radius: 2px; selection-background-color: #0078d7; } -QDoubleSpinBox:focus, QSpinBox:focus, QLineEdit:focus { border-color: #0078d7; } +QDoubleSpinBox, QSpinBox, QLineEdit, QComboBox { background: #3c3c3c; color: #f1f1f1; border: 1px solid #3e3e42; padding: 2px 4px; border-radius: 2px; selection-background-color: #0078d7; } +QDoubleSpinBox:focus, QSpinBox:focus, QLineEdit:focus, QComboBox:focus { border-color: #0078d7; } +QComboBox::drop-down { border-left-width: 1px; border-left-color: #3e3e42; border-left-style: solid; width: 20px; border-top-right-radius: 2px; border-bottom-right-radius: 2px; } +QComboBox::down-arrow { image: none; border: 5px solid transparent; border-top-color: #ccc; margin-top: 5px; } +QAbstractItemView { background-color: #2b2b2b; color: white; border: 1px solid #3e3e42; selection-background-color: #0078d7; outline: 0; } QCheckBox { color: #cccccc; spacing: 5px; } QCheckBox::indicator { width: 14px; height: 14px; border: 1px solid #3e3e42; background: #333337; border-radius: 2px; } QCheckBox::indicator:checked { background: #0078d7; border-color: #005a9e; } @@ -42,6 +49,11 @@ QScrollBar:vertical { background: #1e1e1e; width: 12px; margin: 0px; } QScrollBar::handle:vertical { background: #3e3e42; min-height: 20px; border-radius: 6px; margin: 2px; } QScrollBar::handle:vertical:hover { background: #505050; } QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { height: 0px; } + +/* Dialogs & Preferences */ +QDialog { background-color: #252526; color: #f1f1f1; } +QGroupBox { font-weight: bold; color: #0078d7; border: 1px solid #3e3e42; margin-top: 1.1em; padding-top: 0.5em; border-radius: 4px; } +QGroupBox::title { subcontrol-origin: margin; subcontrol-position: top left; padding: 0 3px; left: 10px; } )"; static const QString BRIGHT_THEME = R"( @@ -49,6 +61,9 @@ QWidget#MenuPanel { background-color: #f3f3f3; border-bottom: 1px solid #ccc; } QLabel#LogoLabel { font-weight: bold; color: #005a9e; font-size: 14px; letter-spacing: 1px; } QPushButton#MenuButton { background: transparent; color: #333; border: none; padding: 5px 10px; } QPushButton#MenuButton:hover { background: #e5e5e5; color: black; border-radius: 4px; } +QPushButton { background-color: #ffffff; color: #333; border: 1px solid #cccccc; padding: 4px 12px; border-radius: 2px; } +QPushButton:hover { background-color: #f2f2f2; border-color: #0078d7; } +QPushButton:pressed { background-color: #0078d7; color: white; } QWidget#PaneTitleBar { background-color: #eeeeee; color: black; border-bottom: 2px solid #ddd; } QLabel#TitleLabel { font-weight: bold; margin-left: 2px; } QToolButton#PaneCloseButton { border: none; font-weight: bold; background: transparent; color: #666; } @@ -66,8 +81,11 @@ QScrollArea > QWidget > QWidget { background: transparent; } /* Property Widgets Styling */ QLabel { color: #333333; } -QDoubleSpinBox, QSpinBox, QLineEdit { background: #ffffff; color: #333333; border: 1px solid #cccccc; padding: 2px 4px; border-radius: 2px; selection-background-color: #0078d7; } -QDoubleSpinBox:focus, QSpinBox:focus, QLineEdit:focus { border-color: #0078d7; } +QDoubleSpinBox, QSpinBox, QLineEdit, QComboBox { background: #ffffff; color: #333333; border: 1px solid #cccccc; padding: 2px 4px; border-radius: 2px; selection-background-color: #0078d7; } +QDoubleSpinBox:focus, QSpinBox:focus, QLineEdit:focus, QComboBox:focus { border-color: #0078d7; } +QComboBox::drop-down { border-left-width: 1px; border-left-color: #cccccc; border-left-style: solid; width: 20px; border-top-right-radius: 2px; border-bottom-right-radius: 2px; } +QComboBox::down-arrow { image: none; border: 5px solid transparent; border-top-color: #666; margin-top: 5px; } +QAbstractItemView { background-color: #ffffff; color: #333; border: 1px solid #cccccc; selection-background-color: #0078d7; outline: 0; } QCheckBox { color: #333333; spacing: 5px; } QCheckBox::indicator { width: 14px; height: 14px; border: 1px solid #cccccc; background: #ffffff; border-radius: 2px; } QCheckBox::indicator:checked { background: #0078d7; border-color: #005a9e; } @@ -85,14 +103,26 @@ QScrollBar:vertical { background: #ffffff; width: 12px; margin: 0px; } QScrollBar::handle:vertical { background: #cccccc; min-height: 20px; border-radius: 6px; margin: 2px; } QScrollBar::handle:vertical:hover { background: #aaaaaa; } QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { height: 0px; } + +/* Dialogs & Preferences */ +QDialog { background-color: #f3f3f3; color: #333; } +QGroupBox { font-weight: bold; color: #005a9e; border: 1px solid #cccccc; margin-top: 1.1em; padding-top: 0.5em; border-radius: 4px; } +QGroupBox::title { subcontrol-origin: margin; subcontrol-position: top left; padding: 0 3px; left: 10px; } )"; -void StyleManager::applyStyle(QApplication* app, const QString& themeName) { +void StyleManager::applyStyle(QApplication* app, const QString& themeName, const uLib::FontConfig& fontCfg) { if (!app) return; - if (themeName == "bright") { - app->setStyleSheet(BRIGHT_THEME); - } else { - app->setStyleSheet(DARK_THEME); // default - } + QString baseStyle = (themeName == "bright") ? BRIGHT_THEME : DARK_THEME; + + QString fontStyle = QString( + "QWidget { font-family: '%1'; font-size: %2pt; }\n" + ).arg(QString::fromStdString(fontCfg.family)) + .arg(fontCfg.size); + + // If bold/italic are needed globally + if (fontCfg.bold) fontStyle += "QWidget { font-weight: bold; }\n"; + if (fontCfg.italic) fontStyle += "QWidget { font-style: italic; }\n"; + + app->setStyleSheet(fontStyle + baseStyle); } diff --git a/app/gcompose/src/StyleManager.h b/app/gcompose/src/StyleManager.h index 5cde819..65f5e62 100644 --- a/app/gcompose/src/StyleManager.h +++ b/app/gcompose/src/StyleManager.h @@ -5,9 +5,11 @@ class QApplication; +namespace uLib { class FontConfig; } + class StyleManager { public: - static void applyStyle(QApplication* app, const QString& themeName); + static void applyStyle(QApplication* app, const QString& themeName, const uLib::FontConfig& font); }; #endif // STYLEMANAGER_H diff --git a/app/gcompose/src/ViewportPane.cpp b/app/gcompose/src/ViewportPane.cpp index 5341ab6..495053d 100644 --- a/app/gcompose/src/ViewportPane.cpp +++ b/app/gcompose/src/ViewportPane.cpp @@ -125,6 +125,10 @@ void ViewportPane::setViewport(QWidget* viewport, const QString& title) { m_areaSplitter->setStretchFactor(0, 1); } +uLib::Vtk::Viewport* ViewportPane::viewport() const { + return dynamic_cast(m_viewport); +} + void ViewportPane::addVtkViewport() { auto* viewport = new uLib::Vtk::QViewport(this); setViewport(viewport, "VTK Viewport"); diff --git a/app/gcompose/src/ViewportPane.h b/app/gcompose/src/ViewportPane.h index fb12642..e88a9d4 100644 --- a/app/gcompose/src/ViewportPane.h +++ b/app/gcompose/src/ViewportPane.h @@ -8,6 +8,7 @@ namespace uLib { class Object; namespace Qt { class PropertyEditor; } + namespace Vtk { class Viewport; } } class QSplitter; @@ -24,6 +25,7 @@ public: void addRootCanvas(); QWidget* currentViewport() const { return m_viewport; } + uLib::Vtk::Viewport* viewport() const; /** @brief Update the display properties for the given object. */ void setObject(uLib::Object* obj); diff --git a/app/gcompose/src/main.cpp b/app/gcompose/src/main.cpp index 81e5eed..c7c7838 100644 --- a/app/gcompose/src/main.cpp +++ b/app/gcompose/src/main.cpp @@ -3,6 +3,8 @@ #include "MainPanel.h" #include "ViewportPane.h" #include "StyleManager.h" +#include "Settings.h" +#include "Core/FontConfig.h" #include "Math/ContainerBox.h" #include @@ -29,7 +31,9 @@ using namespace uLib::literals; int main(int argc, char** argv) { QApplication app(argc, argv); - StyleManager::applyStyle(&app, "dark"); + auto theme = uLib::Qt::Settings::Instance().GetTheme(); + auto initialGuiFont = uLib::Qt::Settings::Instance().GetGuiFont(); + StyleManager::applyStyle(&app, theme == uLib::Qt::Settings::Dark ? "dark" : "bright", initialGuiFont); std::cout << "Starting gcompose Qt application..." << std::endl; // ContainerBox world_box(Vector3f(1, 1, 1)); diff --git a/src/Core/FontConfig.h b/src/Core/FontConfig.h new file mode 100644 index 0000000..defa476 --- /dev/null +++ b/src/Core/FontConfig.h @@ -0,0 +1,48 @@ +#ifndef U_CORE_FONTCONFIG_H +#define U_CORE_FONTCONFIG_H + +#include +#include +#include + +namespace uLib { + +/** + * @struct FontConfig + * @brief Basic font configuration for text properties. + */ +struct FontConfig { + std::string family; + int size; + bool bold; + bool italic; + + FontConfig() : family("Arial"), size(10), bold(false), italic(false) {} + FontConfig(const std::string& fam, int sz, bool b = false, bool i = false) + : family(fam), size(sz), bold(b), italic(i) {} + + bool operator==(const FontConfig& other) const { + return family == other.family && size == other.size && + bold == other.bold && italic == other.italic; + } + bool operator!=(const FontConfig& other) const { return !(*this == other); } + + template + void serialize(Archive& ar, const unsigned int version) { + ar & boost::serialization::make_nvp("family", family); + ar & boost::serialization::make_nvp("size", size); + ar & boost::serialization::make_nvp("bold", bold); + ar & boost::serialization::make_nvp("italic", italic); + } +}; + +inline std::ostream& operator<<(std::ostream& os, const FontConfig& f) { + os << f.family << " " << f.size; + if (f.bold) os << " Bold"; + if (f.italic) os << " Italic"; + return os; +} + +} // namespace uLib + +#endif // U_CORE_FONTCONFIG_H diff --git a/src/Vtk/vtkViewport.cpp b/src/Vtk/vtkViewport.cpp index 2b72c15..7d0f9b1 100644 --- a/src/Vtk/vtkViewport.cpp +++ b/src/Vtk/vtkViewport.cpp @@ -123,10 +123,9 @@ void Viewport::SetupPipeline(vtkRenderWindowInteractor* iren) iren->SetInteractorStyle(style); // Corner annotation - pv->m_Annotation->GetTextProperty()->SetColor(1, 1, 1); - pv->m_Annotation->GetTextProperty()->SetFontFamilyToArial(); + SetFontColor(Vector3d(1.0, 1.0, 1.0)); + SetFont(FontConfig("Arial", 10)); pv->m_Annotation->GetTextProperty()->SetOpacity(0.5); - pv->m_Annotation->SetMaximumFontSize(10); pv->m_Annotation->SetText(0, "uLib VTK viewer."); pv->m_Renderer->AddViewProp(pv->m_Annotation); @@ -603,6 +602,44 @@ bool Viewport::GetParallelProjection() const return false; } +void Viewport::SetFont(const FontConfig& font) { + if (!pv->m_Annotation) return; + auto* prop = pv->m_Annotation->GetTextProperty(); + if (font.family == "Arial") prop->SetFontFamilyToArial(); + else if (font.family == "Courier") prop->SetFontFamilyToCourier(); + else if (font.family == "Times") prop->SetFontFamilyToTimes(); + else prop->SetFontFamilyToArial(); // fallback + + pv->m_Annotation->SetMaximumFontSize(font.size); + prop->SetBold(font.bold); + prop->SetItalic(font.italic); + this->Render(); +} + +FontConfig Viewport::GetFont() const { + if (!pv->m_Annotation) return FontConfig(); + auto* prop = pv->m_Annotation->GetTextProperty(); + FontConfig f; + f.family = prop->GetFontFamilyAsString(); + f.size = pv->m_Annotation->GetMaximumFontSize(); + f.bold = prop->GetBold(); + f.italic = prop->GetItalic(); + return f; +} + +void Viewport::SetFontColor(const Vector3d& color) { + if (!pv->m_Annotation) return; + pv->m_Annotation->GetTextProperty()->SetColor(color.x(), color.y(), color.z()); + this->Render(); +} + +Vector3d Viewport::GetFontColor() const { + if (!pv->m_Annotation) return Vector3d(1,1,1); + double c[3]; + pv->m_Annotation->GetTextProperty()->GetColor(c); + return Vector3d(c[0], c[1], c[2]); +} + void Viewport::SetGridAxis(Axis axis) { m_GridAxis = axis; diff --git a/src/Vtk/vtkViewport.h b/src/Vtk/vtkViewport.h index ead05c1..aa8d67e 100644 --- a/src/Vtk/vtkViewport.h +++ b/src/Vtk/vtkViewport.h @@ -4,6 +4,8 @@ #include "uLibVtkInterface.h" #include #include +#include "Core/FontConfig.h" +#include "Math/Dense.h" namespace uLib { class Object; } @@ -83,6 +85,12 @@ public: void SetParallelProjection(bool parallel); bool GetParallelProjection() const; + // Font configuration + void SetFont(const FontConfig& font); + FontConfig GetFont() const; + void SetFontColor(const Vector3d& color); + Vector3d GetFontColor() const; + protected: void SetupPipeline(vtkRenderWindowInteractor* iren); diff --git a/src/Vtk/vtkViewportProperties.h b/src/Vtk/vtkViewportProperties.h new file mode 100644 index 0000000..650e095 --- /dev/null +++ b/src/Vtk/vtkViewportProperties.h @@ -0,0 +1,56 @@ +#ifndef ULIB_VTK_VIEWPORTPROPERTIES_H +#define ULIB_VTK_VIEWPORTPROPERTIES_H + +#include "uLibVtkInterface.h" +#include "vtkViewport.h" +#include "Core/Property.h" + +namespace uLib { +namespace Vtk { + +/** + * @class ViewportProperties + * @brief Exposes Viewport settings as a uLib::Object for the properties panel. + */ +class ViewportProperties : public uLib::Object { +public: + uLibTypeMacro(ViewportProperties, uLib::Object) + + ViewportProperties(Viewport* vp) : m_Viewport(vp) + { + SetInstanceName("Viewport Settings"); + + // Initialize properties from viewport + Font.Set(vp->GetFont()); + Color.Set(vp->GetFontColor()); + GridVisible.Set(vp->GetGridVisible()); + Parallel.Set(vp->GetParallelProjection()); + + // Connect properties to viewport setters + uLib::Object::connect(&Font, &Property::Updated, [this](){ + if (m_Viewport) m_Viewport->SetFont(Font.Get()); + }); + uLib::Object::connect(&Color, &Property::Updated, [this](){ + if (m_Viewport) m_Viewport->SetFontColor(Color.Get()); + }); + uLib::Object::connect(&GridVisible, &Property::Updated, [this](){ + if (m_Viewport) m_Viewport->SetGridVisible(GridVisible.Get()); + }); + uLib::Object::connect(&Parallel, &Property::Updated, [this](){ + if (m_Viewport) m_Viewport->SetParallelProjection(Parallel.Get()); + }); + } + + ULIB_PROPERTY(FontConfig, Font, FontConfig()) + ULIB_PROPERTY(Vector3d, Color, Vector3d(1.0, 1.0, 1.0)) + ULIB_PROPERTY(bool, GridVisible, true) + ULIB_PROPERTY(bool, Parallel, false) + +private: + Viewport* m_Viewport; +}; + +} // namespace Vtk +} // namespace uLib + +#endif // ULIB_VTK_VIEWPORTPROPERTIES_H