vtkProperties

This commit is contained in:
AndreaRigoni
2026-03-23 17:46:42 +00:00
parent 5d0efb3078
commit f13342ff30
19 changed files with 745 additions and 186 deletions

View File

@@ -13,6 +13,10 @@ add_executable(gcompose
src/ContextModel.cpp
src/StyleManager.h
src/StyleManager.cpp
src/PropertyWidgets.h
src/PropertyWidgets.cpp
src/PropertiesPanel.h
src/PropertiesPanel.cpp
)
set_target_properties(gcompose PROPERTIES

View File

@@ -1,5 +1,7 @@
#include "ContextPanel.h"
#include "ContextModel.h"
#include "PropertyWidgets.h"
#include "PropertiesPanel.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QLabel>
@@ -8,12 +10,10 @@
#include <QList>
#include <QShortcut>
#include <QItemSelectionModel>
#include <Vtk/vtkQViewport.h>
#include <Vtk/vtkObjectsContext.h>
ContextPanel::ContextPanel(QWidget* parent)
: QWidget(parent)
, m_vtkContext(nullptr) {
, m_context(nullptr) {
m_layout = new QVBoxLayout(this);
m_layout->setContentsMargins(0, 0, 0, 0);
m_layout->setSpacing(0);
@@ -43,11 +43,11 @@ ContextPanel::ContextPanel(QWidget* parent)
m_splitter = new QSplitter(Qt::Vertical, this);
m_splitter->addWidget(m_treeView);
m_vtkView = new uLib::Vtk::QViewport(m_splitter);
m_splitter->addWidget(m_vtkView);
m_propertiesPanel = new PropertiesPanel(m_splitter);
m_splitter->addWidget(m_propertiesPanel);
QList<int> sizes;
sizes << 400 << 200;
sizes << 400 << 600;
m_splitter->setSizes(sizes);
m_layout->addWidget(m_splitter);
@@ -60,7 +60,6 @@ ContextPanel::ContextPanel(QWidget* parent)
auto selectedIndexes = m_treeView->selectionModel()->selectedIndexes();
if (selectedIndexes.isEmpty() || !m_context) return;
// Collect objects to remove to avoid iterator invalidation issues if context signal emits during removal
std::vector<uLib::Object*> toRemove;
for (const auto& idx : selectedIndexes) {
if (idx.column() == 0) {
@@ -80,47 +79,6 @@ void ContextPanel::setContext(uLib::ObjectsContext* context) {
m_context = context;
m_model->setContext(context);
m_treeView->expandAll();
if (m_vtkContext) {
m_vtkView->RemovePuppet(*m_vtkContext);
delete m_vtkContext;
}
m_vtkContext = new uLib::Vtk::vtkObjectsContext(context);
// m_vtkView->AddPuppet(*m_vtkContext); // redundant: child puppets are added individually
// Render viewport and add child puppets when context is updated
if (context) {
uLib::Object::connect(m_vtkContext, &uLib::Vtk::vtkObjectsContext::PuppetAdded, [this](uLib::Vtk::Puppet* p) {
if (this->m_vtkView && p) {
this->m_vtkView->AddPuppet(*p);
this->m_vtkView->ZoomAuto();
this->m_vtkView->Render();
}
});
uLib::Object::connect(m_vtkContext, &uLib::Vtk::vtkObjectsContext::PuppetRemoved, [this](uLib::Vtk::Puppet* p) {
if (this->m_vtkView && p) {
this->m_vtkView->RemovePuppet(*p);
this->m_vtkView->Render();
}
});
// Add any puppets that were created during m_vtkContext's construction
for (auto* obj : context->GetObjects()) {
if (auto* p = m_vtkContext->GetPuppet(obj)) {
this->m_vtkView->AddPuppet(*p);
}
}
uLib::Object::connect(context, &uLib::Object::Updated, [this]() {
if (this->m_vtkView) {
this->m_vtkView->ZoomAuto();
this->m_vtkView->Render();
}
});
}
m_vtkView->Render();
}
void ContextPanel::onSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected) {
@@ -131,8 +89,5 @@ void ContextPanel::onSelectionChanged(const QItemSelection& selected, const QIte
emit objectSelected(target);
if (m_vtkContext) {
auto* puppet = m_vtkContext->GetPuppet(target);
m_vtkView->SelectPuppet(puppet);
}
m_propertiesPanel->setObject(target);
}

View File

@@ -1,50 +1,43 @@
#ifndef CONTEXT_PANEL_H
#define CONTEXT_PANEL_H
#ifndef CONTEXTPANEL_H
#define CONTEXTPANEL_H
#include <QWidget>
#include <QItemSelection>
#include "Core/Object.h"
class QTreeView;
class QVBoxLayout;
class QHBoxLayout;
class QLabel;
class ContextModel;
class QTreeView;
class QSplitter;
namespace uLib {
class Object;
class ObjectsContext;
namespace Vtk {
class QViewport;
class vtkObjectsContext;
}
}
class ContextModel;
namespace uLib { class ObjectsContext; }
class ContextPanel : public QWidget {
Q_OBJECT
public:
explicit ContextPanel(QWidget* parent = nullptr);
virtual ~ContextPanel();
ContextPanel(QWidget* parent = nullptr);
~ContextPanel();
void setContext(uLib::ObjectsContext* context);
Q_SIGNALS:
signals:
void objectSelected(uLib::Object* obj);
private Q_SLOTS:
private slots:
void onSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected);
private:
QVBoxLayout* m_layout;
QWidget* m_titleBar;
QLabel* m_titleLabel;
QTreeView* m_treeView;
ContextModel* m_model;
QSplitter* m_splitter;
uLib::Vtk::QViewport* m_vtkView;
uLib::Vtk::vtkObjectsContext* m_vtkContext;
class PropertiesPanel* m_propertiesPanel;
uLib::ObjectsContext* m_context;
};
#endif // CONTEXT_PANEL_H
#endif // CONTEXTPANEL_H

View File

@@ -1,6 +1,7 @@
#include "MainPanel.h"
#include "ViewportPane.h"
#include "ContextPanel.h"
#include "PropertiesPanel.h"
#include "Core/ObjectFactory.h"
#include "Core/ObjectsContext.h"
#include "Vtk/vtkObjectsContext.h"
@@ -74,8 +75,11 @@ MainPanel::MainPanel(QWidget* parent) : QWidget(parent), m_context(nullptr), m_m
m_rootSplitter = new QSplitter(Qt::Horizontal, this);
m_contextPanel = new ContextPanel(m_rootSplitter);
m_rootSplitter->addWidget(m_contextPanel);
m_rootSplitter->setStretchFactor(0, 0);
m_firstPane = new ViewportPane(m_rootSplitter);
m_rootSplitter->addWidget(m_firstPane);
m_rootSplitter->setStretchFactor(1, 1);
connect(m_contextPanel, &ContextPanel::objectSelected, [this](uLib::Object* obj) {
if (auto* viewport = qobject_cast<uLib::Vtk::QViewport*>(m_firstPane->currentViewport())) {
@@ -84,12 +88,16 @@ MainPanel::MainPanel(QWidget* parent) : QWidget(parent), m_context(nullptr), m_m
puppet = m_mainVtkContext->GetPuppet(obj);
}
viewport->SelectPuppet(puppet);
// Update the display properties in the viewport pane itself - use the puppet proxy if possible
m_firstPane->setObject(puppet ? (uLib::Object*)puppet : obj);
} else {
m_firstPane->setObject(obj);
}
});
// Set initial sizes
// Set initial sizes: Context(250), Viewport(600), Properties(250)
QList<int> sizes;
sizes << 200 << 1000;
sizes << 250 << 600 << 250;
m_rootSplitter->setSizes(sizes);
mainLayout->addWidget(m_rootSplitter, 1);

View File

@@ -6,6 +6,7 @@
class QSplitter;
class ViewportPane;
class ContextPanel;
class PropertiesPanel;
namespace uLib {
class ObjectsContext;

View File

@@ -0,0 +1,45 @@
#include "PropertiesPanel.h"
#include "PropertyWidgets.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QLabel>
#include "Core/Object.h"
PropertiesPanel::PropertiesPanel(QWidget* parent) : QWidget(parent) {
this->setObjectName("PropertiesPanel");
m_layout = new QVBoxLayout(this);
m_layout->setContentsMargins(0, 0, 0, 0);
m_layout->setSpacing(0);
// Title bar
m_titleBar = new QWidget(this);
m_titleBar->setObjectName("PaneTitleBar");
m_titleBar->setFixedHeight(22);
auto* titleLayout = new QHBoxLayout(m_titleBar);
titleLayout->setContentsMargins(5, 0, 5, 0);
m_titleLabel = new QLabel("Properties", m_titleBar);
m_titleLabel->setObjectName("TitleLabel");
titleLayout->addWidget(m_titleLabel);
titleLayout->addStretch();
m_layout->addWidget(m_titleBar);
// Editor
m_editor = new uLib::Qt::PropertyEditor(this);
m_layout->addWidget(m_editor, 1);
}
void PropertiesPanel::setObject(uLib::Object* obj) {
if (obj) {
m_titleLabel->setText(QString("Properties: %1 (%2)")
.arg(QString::fromStdString(obj->GetInstanceName()))
.arg(obj->GetClassName()));
} else {
m_titleLabel->setText("Properties: (No selection)");
}
m_editor->setObject(obj);
}
PropertiesPanel::~PropertiesPanel() {}

View File

@@ -0,0 +1,35 @@
#ifndef PROPERTIES_PANEL_H
#define PROPERTIES_PANEL_H
#include <QWidget>
namespace uLib {
class Object;
namespace Qt { class PropertyEditor; }
}
class QVBoxLayout;
class QLabel;
/**
* @class PropertiesPanel
* @brief A panel dedicated to inspecting and editing properties of a selected uLib::Object.
*/
class PropertiesPanel : public QWidget {
Q_OBJECT
public:
explicit PropertiesPanel(QWidget* parent = nullptr);
virtual ~PropertiesPanel();
/** @brief Sets the object to be inspected. */
void setObject(uLib::Object* obj);
private:
QVBoxLayout* m_layout;
QWidget* m_titleBar;
QLabel* m_titleLabel;
uLib::Qt::PropertyEditor* m_editor;
};
#endif // PROPERTIES_PANEL_H

View File

@@ -0,0 +1,173 @@
#include "PropertyWidgets.h"
#include <QSignalBlocker>
#include "Vtk/uLibVtkInterface.h"
namespace uLib {
namespace Qt {
PropertyWidgetBase::PropertyWidgetBase(PropertyBase* prop, QWidget* parent)
: QWidget(parent), m_BaseProperty(prop) {
m_Layout = new QHBoxLayout(this);
m_Layout->setContentsMargins(4, 2, 4, 2);
m_Label = new QLabel(QString::fromStdString(prop->GetName()), this);
m_Label->setMinimumWidth(100);
m_Layout->addWidget(m_Label);
}
PropertyWidgetBase::~PropertyWidgetBase() {}
DoublePropertyWidget::DoublePropertyWidget(Property<double>* prop, QWidget* parent)
: PropertyWidgetBase(prop, parent), m_Prop(prop) {
m_SpinBox = new QDoubleSpinBox(this);
m_SpinBox->setRange(-1000000.0, 1000000.0);
m_SpinBox->setValue(prop->Get());
m_Layout->addWidget(m_SpinBox, 1);
connect(m_SpinBox, static_cast<void(QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged),
[this](double val){ if (m_Prop->Get() != val) m_Prop->Set(val); });
uLib::Object::connect(m_Prop, &Property<double>::PropertyChanged, [this](){
if (m_SpinBox->value() != m_Prop->Get()) {
QSignalBlocker blocker(m_SpinBox);
m_SpinBox->setValue(m_Prop->Get());
}
});
}
DoublePropertyWidget::~DoublePropertyWidget() {}
FloatPropertyWidget::FloatPropertyWidget(Property<float>* prop, QWidget* parent)
: PropertyWidgetBase(prop, parent), m_Prop(prop) {
m_SpinBox = new QDoubleSpinBox(this);
m_SpinBox->setRange(-1000000.0, 1000000.0);
m_SpinBox->setDecimals(3);
m_SpinBox->setValue(prop->Get());
m_Layout->addWidget(m_SpinBox, 1);
connect(m_SpinBox, static_cast<void(QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged),
[this](double val){ if (m_Prop->Get() != (float)val) m_Prop->Set((float)val); });
uLib::Object::connect(m_Prop, &Property<float>::PropertyChanged, [this](){
if (m_SpinBox->value() != (double)m_Prop->Get()) {
QSignalBlocker blocker(m_SpinBox);
m_SpinBox->setValue((double)m_Prop->Get());
}
});
}
FloatPropertyWidget::~FloatPropertyWidget() {}
IntPropertyWidget::IntPropertyWidget(Property<int>* prop, QWidget* parent)
: PropertyWidgetBase(prop, parent), m_Prop(prop) {
m_SpinBox = new QSpinBox(this);
m_SpinBox->setRange(-1000000, 1000000);
m_SpinBox->setValue(prop->Get());
m_Layout->addWidget(m_SpinBox, 1);
connect(m_SpinBox, static_cast<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged),
[this](int val){ if (m_Prop->Get() != val) m_Prop->Set(val); });
uLib::Object::connect(m_Prop, &Property<int>::PropertyChanged, [this](){
if (m_SpinBox->value() != m_Prop->Get()) {
QSignalBlocker blocker(m_SpinBox);
m_SpinBox->setValue(m_Prop->Get());
}
});
}
IntPropertyWidget::~IntPropertyWidget() {}
BoolPropertyWidget::BoolPropertyWidget(Property<bool>* prop, QWidget* parent)
: PropertyWidgetBase(prop, parent), m_Prop(prop) {
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); });
uLib::Object::connect(m_Prop, &Property<bool>::PropertyChanged, [this](){
if (m_CheckBox->isChecked() != m_Prop->Get()) {
QSignalBlocker blocker(m_CheckBox);
m_CheckBox->setChecked(m_Prop->Get());
}
});
}
BoolPropertyWidget::~BoolPropertyWidget() {}
StringPropertyWidget::StringPropertyWidget(Property<std::string>* prop, QWidget* parent)
: PropertyWidgetBase(prop, parent), m_Prop(prop) {
m_LineEdit = new QLineEdit(this);
m_LineEdit->setText(QString::fromStdString(prop->Get()));
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);
});
uLib::Object::connect(m_Prop, &Property<std::string>::PropertyChanged, [this](){
if (m_LineEdit->text().toStdString() != m_Prop->Get()) {
QSignalBlocker blocker(m_LineEdit);
m_LineEdit->setText(QString::fromStdString(m_Prop->Get()));
}
});
}
StringPropertyWidget::~StringPropertyWidget() {}
PropertyEditor::PropertyEditor(QWidget* parent) : QWidget(parent), m_Object(nullptr) {
m_MainLayout = new QVBoxLayout(this);
m_MainLayout->setContentsMargins(0, 0, 0, 0);
m_ScrollArea = new QScrollArea(this);
m_ScrollArea->setWidgetResizable(true);
m_MainLayout->addWidget(m_ScrollArea);
m_Container = new QWidget();
m_ContainerLayout = new QVBoxLayout(m_Container);
m_ContainerLayout->setAlignment(::Qt::AlignTop);
m_ScrollArea->setWidget(m_Container);
registerFactory<double>([](PropertyBase* p, QWidget* parent){
return new DoublePropertyWidget(static_cast<Property<double>*>(p), parent);
});
registerFactory<float>([](PropertyBase* p, QWidget* parent){
return new FloatPropertyWidget(static_cast<Property<float>*>(p), parent);
});
registerFactory<int>([](PropertyBase* p, QWidget* parent){
return new IntPropertyWidget(static_cast<Property<int>*>(p), parent);
});
registerFactory<bool>([](PropertyBase* p, QWidget* parent){
return new BoolPropertyWidget(static_cast<Property<bool>*>(p), parent);
});
registerFactory<std::string>([](PropertyBase* p, QWidget* parent){
return new StringPropertyWidget(static_cast<Property<std::string>*>(p), parent);
});
}
PropertyEditor::~PropertyEditor() {}
void PropertyEditor::setObject(::uLib::Object* obj, bool displayOnly) {
m_Object = obj;
clear();
if (!obj) return;
// Choose which properties to show
const std::vector<::uLib::PropertyBase*>* props = &obj->GetProperties();
if (displayOnly) {
if (auto* puppet = dynamic_cast<::uLib::Vtk::Puppet*>(obj)) {
props = &puppet->GetDisplayProperties();
} else {
// If it's not a puppet but displayOnly is requested, showing nothing or fallback?
// Fallback: core properties.
}
}
for (auto* prop : *props) {
auto it = m_Factories.find(prop->GetTypeIndex());
if (it != m_Factories.end()) {
QWidget* widget = it->second(prop, m_Container);
m_ContainerLayout->addWidget(widget);
} else {
QWidget* fallback = new PropertyWidgetBase(prop, m_Container);
fallback->layout()->addWidget(new QLabel("(Read-only: " + QString::fromStdString(prop->GetValueAsString()) + ")"));
m_ContainerLayout->addWidget(fallback);
}
}
m_ContainerLayout->addStretch(1);
}
void PropertyEditor::clear() {
QLayoutItem* item;
while ((item = m_ContainerLayout->takeAt(0)) != nullptr) {
delete item->widget();
delete item;
}
}
} // namespace Qt
} // namespace uLib

View File

@@ -0,0 +1,110 @@
#ifndef PROPERTY_WIDGETS_H
#define PROPERTY_WIDGETS_H
#include <QWidget>
#include <QLabel>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QDoubleSpinBox>
#include <QSpinBox>
#include <QLineEdit>
#include <QCheckBox>
#include <QScrollArea>
#include <map>
#include <typeindex>
#include <functional>
#include "Core/Property.h"
#include "Core/Object.h"
namespace uLib {
namespace Qt {
class PropertyWidgetBase : public QWidget {
Q_OBJECT
public:
PropertyWidgetBase(PropertyBase* prop, QWidget* parent = nullptr);
virtual ~PropertyWidgetBase();
PropertyBase* getProperty() const { return m_BaseProperty; }
protected:
PropertyBase* m_BaseProperty;
QHBoxLayout* m_Layout;
QLabel* m_Label;
};
class DoublePropertyWidget : public PropertyWidgetBase {
Q_OBJECT
public:
DoublePropertyWidget(Property<double>* prop, QWidget* parent = nullptr);
virtual ~DoublePropertyWidget();
private:
Property<double>* m_Prop;
QDoubleSpinBox* m_SpinBox;
};
class FloatPropertyWidget : public PropertyWidgetBase {
Q_OBJECT
public:
FloatPropertyWidget(Property<float>* prop, QWidget* parent = nullptr);
virtual ~FloatPropertyWidget();
private:
Property<float>* m_Prop;
QDoubleSpinBox* m_SpinBox;
};
class IntPropertyWidget : public PropertyWidgetBase {
Q_OBJECT
public:
IntPropertyWidget(Property<int>* prop, QWidget* parent = nullptr);
virtual ~IntPropertyWidget();
private:
Property<int>* m_Prop;
QSpinBox* m_SpinBox;
};
class BoolPropertyWidget : public PropertyWidgetBase {
Q_OBJECT
public:
BoolPropertyWidget(Property<bool>* prop, QWidget* parent = nullptr);
virtual ~BoolPropertyWidget();
private:
Property<bool>* m_Prop;
QCheckBox* m_CheckBox;
};
class StringPropertyWidget : public PropertyWidgetBase {
Q_OBJECT
public:
StringPropertyWidget(Property<std::string>* prop, QWidget* parent = nullptr);
virtual ~StringPropertyWidget();
private:
Property<std::string>* m_Prop;
QLineEdit* m_LineEdit;
};
class PropertyEditor : public QWidget {
Q_OBJECT
public:
PropertyEditor(QWidget* parent = nullptr);
virtual ~PropertyEditor();
void setObject(uLib::Object* obj, bool displayOnly = false);
template<typename T>
void registerFactory(std::function<QWidget*(PropertyBase*, QWidget*)> factory) {
m_Factories[std::type_index(typeid(T))] = factory;
}
private:
void clear();
uLib::Object* m_Object;
QVBoxLayout* m_MainLayout;
QScrollArea* m_ScrollArea;
QWidget* m_Container;
QVBoxLayout* m_ContainerLayout;
std::map<std::type_index, std::function<QWidget*(PropertyBase*, QWidget*)>> m_Factories;
};
} // namespace Qt
} // namespace uLib
#endif // PROPERTY_WIDGETS_H

View File

@@ -9,6 +9,8 @@
#include <QAction>
#include <QSplitter>
#include <vtkCamera.h>
#include "PropertyWidgets.h"
#include <QSignalBlocker>
QViewportPane::QViewportPane(QWidget* parent) : QWidget(parent), m_viewport(nullptr) {
m_layout = new QVBoxLayout(this);
@@ -21,11 +23,16 @@ QViewportPane::QViewportPane(QWidget* parent) : QWidget(parent), m_viewport(null
m_titleBar->setFixedHeight(22);
auto* titleLayout = new QHBoxLayout(m_titleBar);
titleLayout->setContentsMargins(5, 0, 5, 0);
titleLayout->setContentsMargins(5, 0, 0, 0);
m_titleLabel = new QLabel("Viewport", m_titleBar);
m_titleLabel->setObjectName("TitleLabel");
m_toggleBtn = new QPushButton("Display", m_titleBar);
m_toggleBtn->setCheckable(true);
m_toggleBtn->setFixedSize(60, 18);
m_toggleBtn->setObjectName("DisplayToggleBtn");
auto* closeBtn = new QToolButton(m_titleBar);
closeBtn->setObjectName("PaneCloseButton");
closeBtn->setText("X");
@@ -33,17 +40,72 @@ QViewportPane::QViewportPane(QWidget* parent) : QWidget(parent), m_viewport(null
titleLayout->addWidget(m_titleLabel);
titleLayout->addStretch();
titleLayout->addWidget(m_toggleBtn);
titleLayout->addSpacing(5);
titleLayout->addWidget(closeBtn);
m_layout->addWidget(m_titleBar);
m_titleBar->setContextMenuPolicy(Qt::CustomContextMenu);
// Main horizontal container for viewport and display panel
QWidget* mainArea = new QWidget(this);
QHBoxLayout* hLayout = new QHBoxLayout(mainArea);
hLayout->setContentsMargins(0, 0, 0, 0);
hLayout->setSpacing(0);
m_layout->addWidget(mainArea);
// Viewport will be added here via setViewport
m_viewport = new uLib::Vtk::QViewport(mainArea);
hLayout->addWidget(m_viewport);
// Display Panel (Overlay/Slide-out)
m_displayPanel = new QFrame(mainArea);
m_displayPanel->setObjectName("DisplayPropertiesPanel");
m_displayPanel->setFixedWidth(250);
m_displayPanel->hide();
QVBoxLayout* panelLayout = new QVBoxLayout(m_displayPanel);
panelLayout->setContentsMargins(5, 5, 5, 5);
QLabel* panelHeader = new QLabel("Display Properties", m_displayPanel);
panelHeader->setStyleSheet("font-weight: bold; padding: 5px;");
panelLayout->addWidget(panelHeader);
m_displayEditor = new uLib::Qt::PropertyEditor(m_displayPanel);
panelLayout->addWidget(m_displayEditor);
hLayout->addWidget(m_displayPanel);
connect(m_toggleBtn, &QPushButton::toggled, this, &QViewportPane::toggleDisplayPanel);
connect(m_titleBar, &QWidget::customContextMenuRequested, this, &QViewportPane::showContextMenu);
connect(closeBtn, &QToolButton::clicked, this, &QViewportPane::onCloseRequested);
addVtkViewport(); // Initialize with a default VTK viewport
m_titleBar->setContextMenuPolicy(Qt::CustomContextMenu);
}
void QViewportPane::toggleDisplayPanel() {
m_displayPanel->setVisible(m_toggleBtn->isChecked());
}
void QViewportPane::setObject(uLib::Object* obj) {
m_displayEditor->setObject(obj, true);
// Auto-show panel if it's a puppet and we want to highlight this feature?
// User asked for "hiding panel", so maybe we don't auto-show.
}
void QViewportPane::setViewport(QWidget* viewport, const QString& title) {
if (m_viewport) {
m_viewport->parentWidget()->layout()->removeWidget(m_viewport);
delete m_viewport;
}
m_viewport = viewport;
m_titleLabel->setText(title);
m_viewport->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
auto* mainAreaLayout = static_cast<QHBoxLayout*>(m_displayPanel->parentWidget()->layout());
mainAreaLayout->insertWidget(0, m_viewport);
}
QViewportPane::~QViewportPane() {}
void QViewportPane::setViewport(QWidget* viewport, const QString& title) {

View File

@@ -2,6 +2,13 @@
#define QVIEWPORTPANE_H
#include <QWidget>
#include <QFrame>
#include <QPushButton>
namespace uLib {
class Object;
namespace Qt { class PropertyEditor; }
}
class QVBoxLayout;
class QLabel;
@@ -16,10 +23,14 @@ public:
void addRootCanvas();
QWidget* currentViewport() const { return m_viewport; }
/** @brief Update the display properties for the given object. */
void setObject(uLib::Object* obj);
private slots:
void onCloseRequested();
void showContextMenu(const QPoint& pos);
void toggleDisplayPanel();
private:
void AttemptSplit(Qt::Orientation orientation);
@@ -29,6 +40,11 @@ private:
QWidget* m_titleBar;
QLabel* m_titleLabel;
QWidget* m_viewport;
// Display Properties Overlay
QFrame* m_displayPanel;
uLib::Qt::PropertyEditor* m_displayEditor;
QPushButton* m_toggleBtn;
};
#endif // QVIEWPORTPANE_H

View File

@@ -6,31 +6,83 @@ 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; }
QWidget#PaneTitleBar { background-color: #333; color: white; }
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; }
QToolButton#PaneCloseButton:hover { color: white; background: red; }
QToolButton#PaneCloseButton:hover { color: white; background: #c42b1c; }
/* Viewport & Properties Panels */
QWidget#DisplayPropertiesPanel, QWidget#PropertiesPanel { background-color: #252526; border-left: 1px solid #3e3e42; }
QPushButton#DisplayToggleBtn { background-color: #333337; border: 1px solid #3e3e42; border-radius: 2px; color: #f1f1f1; font-size: 11px; }
QPushButton#DisplayToggleBtn:checked { background-color: #0078d7; color: white; border-color: #005a9e; font-weight: bold; }
QPushButton#DisplayToggleBtn:hover { border-color: #0078d7; }
QScrollArea { border: none; background: transparent; }
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; }
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; }
QCheckBox::indicator:hover { border-color: #0078d7; }
QMenu { background-color: #2b2b2b; color: white; border: 1px solid #111; }
QMenu::item:selected { background-color: #3c3c3c; }
QTreeView#ContextTree { background-color: #1e1e1e; color: #ccc; border: none; }
QTreeView#ContextTree::item:hover { background-color: #2a2d2e; }
QTreeView#ContextTree::item:selected { background-color: #094771; color: white; }
QHeaderView::section { background-color: #252526; color: #ccc; border: 1px solid #323233; padding: 4px; }
/* ScrollBars */
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; }
)";
static const QString BRIGHT_THEME = R"(
QWidget#MenuPanel { background-color: #f0f0f0; border-bottom: 1px solid #ccc; }
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: #d0d0d0; color: black; border-radius: 4px; }
QWidget#PaneTitleBar { background-color: #e0e0e0; color: black; }
QPushButton#MenuButton:hover { background: #e5e5e5; color: black; border-radius: 4px; }
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; }
QToolButton#PaneCloseButton:hover { color: white; background: #e81123; }
QMenu { background-color: #f0f0f0; color: black; border: 1px solid #ccc; }
/* Viewport & Properties Panels */
QWidget#DisplayPropertiesPanel { background-color: #ffffff; border-left: 1px solid #cccccc; }
QPushButton#DisplayToggleBtn { background-color: #ffffff; border: 1px solid #cccccc; border-radius: 2px; color: #333; font-size: 11px; }
QPushButton#DisplayToggleBtn:checked { background-color: #0078d7; color: white; border-color: #005a9e; font-weight: bold; }
QPushButton#DisplayToggleBtn:hover { border-color: #0078d7; }
QScrollArea { border: none; background: transparent; }
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; }
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; }
QCheckBox::indicator:hover { border-color: #0078d7; }
QMenu { background-color: #f3f3f3; color: black; border: 1px solid #ccc; }
QMenu::item:selected { background-color: #d0d0d0; }
QTreeView#ContextTree { background-color: #ffffff; color: #333; border: none; }
QTreeView#ContextTree::item:hover { background-color: #f2f2f2; }
QTreeView#ContextTree::item:selected { background-color: #0078d7; color: white; }
QHeaderView::section { background-color: #f3f3f3; color: #333; border: 1px solid #ccc; padding: 4px; }
/* ScrollBars */
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; }
)";
void StyleManager::applyStyle(QApplication* app, const QString& themeName) {

View File

@@ -9,6 +9,8 @@
#include <QAction>
#include <QSplitter>
#include <vtkCamera.h>
#include "PropertyWidgets.h"
#include <QSignalBlocker>
ViewportPane::ViewportPane(QWidget* parent) : QWidget(parent), m_viewport(nullptr) {
m_layout = new QVBoxLayout(this);
@@ -21,11 +23,16 @@ ViewportPane::ViewportPane(QWidget* parent) : QWidget(parent), m_viewport(nullpt
m_titleBar->setFixedHeight(22);
auto* titleLayout = new QHBoxLayout(m_titleBar);
titleLayout->setContentsMargins(5, 0, 5, 0);
titleLayout->setContentsMargins(5, 0, 0, 0);
m_titleLabel = new QLabel("Viewport", m_titleBar);
m_titleLabel->setObjectName("TitleLabel");
m_toggleBtn = new QPushButton("Display", m_titleBar);
m_toggleBtn->setCheckable(true);
m_toggleBtn->setFixedSize(60, 18);
m_toggleBtn->setObjectName("DisplayToggleBtn");
auto* closeBtn = new QToolButton(m_titleBar);
closeBtn->setObjectName("PaneCloseButton");
closeBtn->setText("X");
@@ -33,29 +40,82 @@ ViewportPane::ViewportPane(QWidget* parent) : QWidget(parent), m_viewport(nullpt
titleLayout->addWidget(m_titleLabel);
titleLayout->addStretch();
titleLayout->addWidget(m_toggleBtn);
titleLayout->addSpacing(5);
titleLayout->addWidget(closeBtn);
m_layout->addWidget(m_titleBar);
m_titleBar->setContextMenuPolicy(Qt::CustomContextMenu);
// Main horizontal container for viewport and display panel
QWidget* mainArea = new QWidget(this);
QHBoxLayout* hLayout = new QHBoxLayout(mainArea);
hLayout->setContentsMargins(0, 0, 0, 0);
hLayout->setSpacing(0);
m_layout->addWidget(mainArea);
// Viewport will be added here via setViewport
m_viewport = new uLib::Vtk::QViewport(mainArea);
hLayout->addWidget(m_viewport);
// Display Panel (Overlay/Slide-out)
m_displayPanel = new QFrame(mainArea);
m_displayPanel->setObjectName("DisplayPropertiesPanel");
m_displayPanel->setFixedWidth(250);
m_displayPanel->hide();
QVBoxLayout* panelLayout = new QVBoxLayout(m_displayPanel);
panelLayout->setContentsMargins(5, 5, 5, 5);
QLabel* panelHeader = new QLabel("Display Properties", m_displayPanel);
panelHeader->setStyleSheet("font-weight: bold; padding: 5px;");
panelLayout->addWidget(panelHeader);
m_displayEditor = new uLib::Qt::PropertyEditor(m_displayPanel);
panelLayout->addWidget(m_displayEditor);
hLayout->addWidget(m_displayPanel);
connect(m_toggleBtn, &QPushButton::toggled, this, &ViewportPane::toggleDisplayPanel);
connect(m_titleBar, &QWidget::customContextMenuRequested, this, &ViewportPane::showContextMenu);
connect(closeBtn, &QToolButton::clicked, this, &ViewportPane::onCloseRequested);
addVtkViewport(); // Initialize with a default VTK viewport
m_titleBar->setContextMenuPolicy(Qt::CustomContextMenu);
}
ViewportPane::~ViewportPane() {}
void ViewportPane::toggleDisplayPanel() {
m_displayPanel->setVisible(m_toggleBtn->isChecked());
}
void ViewportPane::setObject(uLib::Object* obj) {
m_displayEditor->setObject(obj, true);
// Check if the object is a Puppet (meaning it has display properties)
bool isPuppet = (dynamic_cast<::uLib::Vtk::Puppet*>(obj) != nullptr);
// Only show the "Display" toggle button if it's a puppet
m_toggleBtn->setVisible(isPuppet);
// If it's a puppet, we might want to keep the panel state if it was already open,
// or if it's NOT a puppet, definitely hide the toggle and panel.
if (!isPuppet) {
m_toggleBtn->setChecked(false);
m_displayPanel->hide();
}
}
void ViewportPane::setViewport(QWidget* viewport, const QString& title) {
if (m_viewport) {
m_layout->removeWidget(m_viewport);
m_viewport->parentWidget()->layout()->removeWidget(m_viewport);
delete m_viewport;
}
m_viewport = viewport;
m_titleLabel->setText(title);
m_viewport->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
m_layout->addWidget(m_viewport);
auto* mainAreaLayout = static_cast<QHBoxLayout*>(m_displayPanel->parentWidget()->layout());
mainAreaLayout->insertWidget(0, m_viewport);
}
void ViewportPane::addVtkViewport() {
@@ -73,7 +133,6 @@ void ViewportPane::onCloseRequested() {
if (parentSplitter && parentSplitter->count() > 1) {
deleteLater();
} else {
// Can't close the last viewport in the splitter safely. Re-initialize to default VTK canvas.
addVtkViewport();
}
}
@@ -83,64 +142,20 @@ void ViewportPane::showContextMenu(const QPoint& pos) {
QAction* hSplit = menu.addAction("H split");
QAction* vSplit = menu.addAction("V split");
menu.addSeparator();
bool isVtk = (qobject_cast<uLib::Vtk::QViewport*>(m_viewport) != nullptr);
QAction* changeType = menu.addAction(isVtk ? "Change to ROOT Canvas" : "Change to VTK Viewport");
QAction* selected = menu.exec(m_titleBar->mapToGlobal(pos));
if (selected == hSplit) {
AttemptSplit(Qt::Horizontal);
} else if (selected == vSplit) {
AttemptSplit(Qt::Vertical);
} else if (selected == changeType) {
if (isVtk) {
addRootCanvas();
} else {
addVtkViewport();
}
}
if (selected == hSplit) AttemptSplit(Qt::Horizontal);
else if (selected == vSplit) AttemptSplit(Qt::Vertical);
else if (selected == changeType) isVtk ? addRootCanvas() : addVtkViewport();
}
void ViewportPane::AttemptSplit(Qt::Orientation orientation) {
QWidget* p = parentWidget();
if (!p) return;
QSplitter* parentSplitter = qobject_cast<QSplitter*>(p);
if (!parentSplitter) return;
ViewportPane* newPane = new ViewportPane();
// 1. Synchronize viewport content and camera (VTK Viewport only for now)
auto* currentVtk = qobject_cast<uLib::Vtk::QViewport*>(m_viewport);
if (currentVtk) {
auto* newVtk = qobject_cast<uLib::Vtk::QViewport*>(newPane->currentViewport());
if (newVtk) {
// Copy puppets
for (auto* puppet : currentVtk->getPuppets()) {
newVtk->AddPuppet(*puppet);
}
// Copy camera
if (currentVtk->GetRenderer() && newVtk->GetRenderer()) {
vtkCamera* currentCam = currentVtk->GetRenderer()->GetActiveCamera();
vtkCamera* newCam = newVtk->GetRenderer()->GetActiveCamera();
if (currentCam && newCam) {
newCam->DeepCopy(currentCam);
}
}
// Sync grid visible and axis
newVtk->SetGridVisible(currentVtk->GetGridVisible());
newVtk->SetGridAxis(currentVtk->GetGridAxis());
}
}
// 2. Adjust for ROOT Canvas if that was the active view
bool isRoot = (qobject_cast<uLib::Root::QCanvas*>(m_viewport) != nullptr);
if (isRoot) {
newPane->addRootCanvas();
}
if (parentSplitter->orientation() == orientation) {
int index = parentSplitter->indexOf(this);
QList<int> sizes = parentSplitter->sizes();
@@ -148,21 +163,17 @@ void ViewportPane::AttemptSplit(Qt::Orientation orientation) {
int half = currentSize / 2;
sizes[index] = half;
sizes.insert(index + 1, currentSize - half);
parentSplitter->insertWidget(index + 1, newPane);
parentSplitter->setSizes(sizes);
} else {
int index = parentSplitter->indexOf(this);
QList<int> parentSizes = parentSplitter->sizes();
QSplitter* newSplitter = new QSplitter(orientation);
newSplitter->addWidget(this);
newSplitter->addWidget(newPane);
QList<int> subSizes;
subSizes << 500 << 500;
newSplitter->setSizes(subSizes);
parentSplitter->insertWidget(index, newSplitter);
parentSplitter->setSizes(parentSizes);
}

View File

@@ -2,6 +2,13 @@
#define VIEWPORTPANE_H
#include <QWidget>
#include <QFrame>
#include <QPushButton>
namespace uLib {
class Object;
namespace Qt { class PropertyEditor; }
}
class QVBoxLayout;
class QLabel;
@@ -16,10 +23,14 @@ public:
void addRootCanvas();
QWidget* currentViewport() const { return m_viewport; }
/** @brief Update the display properties for the given object. */
void setObject(uLib::Object* obj);
private slots:
void onCloseRequested();
void showContextMenu(const QPoint& pos);
void toggleDisplayPanel();
private:
void AttemptSplit(Qt::Orientation orientation);
@@ -29,6 +40,11 @@ private:
QWidget* m_titleBar;
QLabel* m_titleLabel;
QWidget* m_viewport;
// Display Properties Overlay
QFrame* m_displayPanel;
uLib::Qt::PropertyEditor* m_displayEditor;
QPushButton* m_toggleBtn;
};
#endif // VIEWPORTPANE_H