feat: implement configurable font settings for VTK viewports and GUI elements with persistent preferences.

This commit is contained in:
AndreaRigoni
2026-04-15 14:50:46 +00:00
parent bf4006ff91
commit 24ec326715
20 changed files with 433 additions and 28 deletions

View File

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

View File

@@ -25,6 +25,7 @@ public:
signals:
void objectSelected(uLib::Object* obj);
void propertyUpdated();
private slots:
void onSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected);

View File

@@ -6,6 +6,8 @@
#include "Core/ObjectsContext.h"
#include "Vtk/vtkObjectsContext.h"
#include "Vtk/vtkQViewport.h"
#include "Vtk/vtkViewportProperties.h"
#include <Vtk/uLibVtkInterface.h>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QSplitter>
@@ -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<uLib::Vtk::QViewport*>();
for (auto* vp : viewports) {
vp->Render();
}
});
// Set initial sizes: Context(250), Viewport(600), Properties(250)
QList<int> 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<uLib::Vtk::QViewport*>();
for (auto* vp : viewports) {
vp->SetThrottledRendering(throttled);
vp->SetFont(font);
vp->SetFontColor(fontColor);
vp->Render();
}
}

View File

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

View File

@@ -5,6 +5,8 @@
#include <QPushButton>
#include <QLabel>
#include <QGroupBox>
#include <QColorDialog>
#include <QFormLayout>
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

View File

@@ -4,6 +4,8 @@
#include <QDialog>
#include <QCheckBox>
#include <QComboBox>
#include <QSpinBox>
#include <QPushButton>
#include <map>
#include <string>
#include "Settings.h"
@@ -23,6 +25,22 @@ private:
QCheckBox* m_throttledRendering;
QComboBox* m_themeCombo;
std::map<Settings::Dimension, QComboBox*> 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

View File

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

View File

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

View File

@@ -11,6 +11,7 @@
#include <QColorDialog>
#include <QFrame>
#include <QSlider>
#include <QFontDialog>
#include "Settings.h"
namespace uLib {
@@ -151,7 +152,7 @@ DoublePropertyWidget::DoublePropertyWidget(Property<double>* 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<double>::Updated, [this](){
m_Edit->setValue(m_Prop->Get());
});
@@ -169,7 +170,7 @@ FloatPropertyWidget::FloatPropertyWidget(Property<float>* 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<float>::Updated, [this](){
m_Edit->setValue((double)m_Prop->Get());
});
@@ -188,7 +189,7 @@ IntPropertyWidget::IntPropertyWidget(Property<int>* 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<int>::Updated, [this](){
m_Edit->setValue((double)m_Prop->Get());
});
@@ -199,7 +200,7 @@ BoolPropertyWidget::BoolPropertyWidget(Property<bool>* 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<bool>::Updated, [this](){
if (m_CheckBox->isChecked() != m_Prop->Get()) {
QSignalBlocker blocker(m_CheckBox);
@@ -222,7 +223,7 @@ RangePropertyWidget::RangePropertyWidget(Property<double>* 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<double>::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<Vector3d>* 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<std::string>* 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<std::string>::Updated, [this](){
if (m_LineEdit->text().toStdString() != m_Prop->Get()) {
@@ -297,6 +300,40 @@ StringPropertyWidget::StringPropertyWidget(Property<std::string>* prop, QWidget*
}
StringPropertyWidget::~StringPropertyWidget() {}
FontPropertyWidget::FontPropertyWidget(Property<FontConfig>* 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<FontConfig>::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<Property<int>*>(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<int>::Updated, [this, p](){
@@ -374,6 +412,9 @@ PropertyEditor::PropertyEditor(QWidget* parent) : QWidget(parent), m_Object(null
registerFactory<std::string>([](PropertyBase* p, QWidget* parent){
return new StringPropertyWidget(static_cast<Property<std::string>*>(p), parent);
});
registerFactory<FontConfig>([](PropertyBase* p, QWidget* parent){
return new FontPropertyWidget(static_cast<Property<FontConfig>*>(p), parent);
});
// Register EnumProperty specifically (needs to check type since it holds Property<int> 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<PropertyWidgetBase*>(widget)) {
connect(propWidget, &PropertyWidgetBase::updated, [this, prop](){
emit propertyUpdated(prop);
});
}
if (!groupName.empty()) {
// Indent grouped properties
widget->setContentsMargins(16, 0, 0, 0);

View File

@@ -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<FontConfig>* prop, QWidget* parent = nullptr);
virtual ~FontPropertyWidget();
private slots:
void onClicked();
private:
void updateButtonText();
Property<FontConfig>* 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;

View File

@@ -4,6 +4,8 @@
#include <string>
#include <map>
#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<Dimension, std::string> m_PreferredUnits;
bool m_ThrottledRendering;
Theme m_Theme;
FontConfig m_Font;
FontConfig m_GuiFont;
Vector3d m_FontColor;
};
} // namespace Qt

View File

@@ -1,11 +1,15 @@
#include "StyleManager.h"
#include <QApplication>
#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);
}

View File

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

View File

@@ -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<uLib::Vtk::Viewport*>(m_viewport);
}
void ViewportPane::addVtkViewport() {
auto* viewport = new uLib::Vtk::QViewport(this);
setViewport(viewport, "VTK Viewport");

View File

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

View File

@@ -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 <HEP/Geant/Scene.h>
@@ -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));

48
src/Core/FontConfig.h Normal file
View File

@@ -0,0 +1,48 @@
#ifndef U_CORE_FONTCONFIG_H
#define U_CORE_FONTCONFIG_H
#include <string>
#include <ostream>
#include <boost/serialization/nvp.hpp>
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<class Archive>
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

View File

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

View File

@@ -4,6 +4,8 @@
#include "uLibVtkInterface.h"
#include <vector>
#include <map>
#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);

View File

@@ -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<FontConfig>::Updated, [this](){
if (m_Viewport) m_Viewport->SetFont(Font.Get());
});
uLib::Object::connect(&Color, &Property<Vector3d>::Updated, [this](){
if (m_Viewport) m_Viewport->SetFontColor(Color.Get());
});
uLib::Object::connect(&GridVisible, &Property<bool>::Updated, [this](){
if (m_Viewport) m_Viewport->SetGridVisible(GridVisible.Get());
});
uLib::Object::connect(&Parallel, &Property<bool>::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