#include "panes/propertiesview.h" #include #include #include #include #include class PropertiesItemDelegate : public QStyledItemDelegate { PropertiesView* view; public: PropertiesItemDelegate(PropertiesView* parent) : view(parent), QStyledItemDelegate(parent) {} void initStyleOption(QStyleOptionViewItem *option, const QModelIndex &index) const override { // https://stackoverflow.com/a/76645757/16255372 // https://stackoverflow.com/a/70078448/16255372 int indent = dynamic_cast(parent())->indentation(); QStyledItemDelegate::initStyleOption(option, index); if (!index.parent().isValid()) { option->state &= ~QStyle::State_Selected; option->backgroundBrush = view->palette().dark(); } }; QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override { if (index.column() == 0) return nullptr; if (!index.parent().isValid() || !view->currentInstance || view->currentInstance->expired()) return nullptr; InstanceRef inst = view->currentInstance->lock(); std::string propertyName = view->itemFromIndex(index)->data(0, Qt::DisplayRole).toString().toStdString(); PropertyMeta meta = inst->GetPropertyMeta(propertyName).value(); Data::Variant currentValue = inst->GetPropertyValue(propertyName).value(); if (meta.type == &Data::Float::TYPE) { QDoubleSpinBox* spinBox = new QDoubleSpinBox(parent); spinBox->setValue(currentValue.get()); if (meta.flags & PROP_UNIT_FLOAT) { spinBox->setMinimum(0); spinBox->setMaximum(1); spinBox->setSingleStep(0.1); } return spinBox; } else if (meta.type == &Data::Int::TYPE) { QSpinBox* spinBox = new QSpinBox(parent); spinBox->setValue(currentValue.get()); return spinBox; } else if (meta.type == &Data::String::TYPE) { QLineEdit* lineEdit = new QLineEdit(parent); lineEdit->setText(QString::fromStdString(currentValue.get())); return lineEdit; } else if (meta.type == &Data::Color3::TYPE) { QColorDialog* colorDialog = new QColorDialog(parent->window()); Data::Color3 color = currentValue.get(); colorDialog->setCurrentColor(QColor::fromRgbF(color.R(), color.G(), color.B())); return colorDialog; } else if (meta.type->fromString) { QLineEdit* lineEdit = new QLineEdit(parent); lineEdit->setText(QString::fromStdString(currentValue.ToString())); return lineEdit; } return nullptr; } void setEditorData(QWidget *editor, const QModelIndex &index) const override { if (index.column() == 0) return; if (!index.parent().isValid() || !view->currentInstance || view->currentInstance->expired()) return; InstanceRef inst = view->currentInstance->lock(); std::string propertyName = view->itemFromIndex(index)->data(0, Qt::DisplayRole).toString().toStdString(); PropertyMeta meta = inst->GetPropertyMeta(propertyName).value(); Data::Variant currentValue = inst->GetPropertyValue(propertyName).value(); if (meta.type == &Data::Float::TYPE) { QDoubleSpinBox* spinBox = dynamic_cast(editor); spinBox->setValue(currentValue.get()); } else if (meta.type == &Data::Int::TYPE) { QSpinBox* spinBox = dynamic_cast(editor); spinBox->setValue(currentValue.get()); } else if (meta.type == &Data::String::TYPE) { QLineEdit* lineEdit = dynamic_cast(editor); lineEdit->setText(QString::fromStdString((std::string)currentValue.get())); } else if (meta.type == &Data::Color3::TYPE) { QColorDialog* colorDialog = dynamic_cast(editor); Data::Color3 color = currentValue.get(); colorDialog->setCurrentColor(QColor::fromRgbF(color.R(), color.G(), color.B())); } else if (meta.type->fromString) { QLineEdit* lineEdit = dynamic_cast(editor); lineEdit->setText(QString::fromStdString((std::string)currentValue.ToString())); } } void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override { if (index.column() == 0) return; if (!index.parent().isValid() || !view->currentInstance || view->currentInstance->expired()) return; InstanceRef inst = view->currentInstance->lock(); std::string propertyName = view->itemFromIndex(index)->data(0, Qt::DisplayRole).toString().toStdString(); PropertyMeta meta = inst->GetPropertyMeta(propertyName).value(); if (meta.type == &Data::Float::TYPE) { QDoubleSpinBox* spinBox = dynamic_cast(editor); inst->SetPropertyValue(propertyName, Data::Float((float)spinBox->value())); model->setData(index, spinBox->value()); } else if (meta.type == &Data::Int::TYPE) { QSpinBox* spinBox = dynamic_cast(editor); inst->SetPropertyValue(propertyName, Data::Int((float)spinBox->value())); model->setData(index, spinBox->value()); } else if (meta.type == &Data::String::TYPE) { QLineEdit* lineEdit = dynamic_cast(editor); inst->SetPropertyValue(propertyName, Data::String(lineEdit->text().toStdString())); model->setData(index, lineEdit->text()); } else if (meta.type == &Data::Color3::TYPE) { QColorDialog* colorDialog = dynamic_cast(editor); QColor color = colorDialog->currentColor(); Data::Color3 color3(color.redF(), color.greenF(), color.blueF()); inst->SetPropertyValue(propertyName, color3); model->setData(index, QString::fromStdString(color3.ToString()), Qt::DisplayRole); model->setData(index, color, Qt::DecorationRole); } else if (meta.type->fromString) { QLineEdit* lineEdit = dynamic_cast(editor); std::optional parsedResult = meta.type->fromString(lineEdit->text().toStdString()); if (!parsedResult) return; inst->SetPropertyValue(propertyName, parsedResult.value()); model->setData(index, QString::fromStdString(parsedResult.value().ToString())); } } }; PropertiesView::PropertiesView(QWidget* parent): QTreeWidget(parent) { clear(); setHeaderHidden(true); setColumnCount(2); setAlternatingRowColors(true); setItemDelegate(new PropertiesItemDelegate(this)); connect(this, &QTreeWidget::itemChanged, this, &PropertiesView::propertyChanged); connect(this, &QTreeWidget::itemActivated, this, [&](auto* item, int column) { // Prevent editing the first column if (column == 0) item->setFlags(item->flags() & ~Qt::ItemIsEditable); else if (item->parent()) item->setFlags(item->flags() | Qt::ItemIsEditable); }); } PropertiesView::~PropertiesView() { } QStringList PROPERTY_CATEGORY_NAMES { "Appearence", "Data", "Behavior", "Part", "Surface" }; QModelIndex PropertiesView::indexAt(const QPoint &point) const { return QTreeWidget::indexAt(point + QPoint(indentation(), 0)); } void PropertiesView::drawBranches(QPainter *painter, const QRect &rect, const QModelIndex &index) const { // https://codebrowser.dev/qt5/qtbase/src/widgets/itemviews/qtreeview.cpp.html#312opt // Draw background for headings if (!index.parent().isValid()) painter->fillRect(rect, palette().dark()); QTreeWidget::drawBranches(painter, rect, index); } void PropertiesView::setSelected(std::optional instance) { clear(); if (!instance) return; InstanceRef inst = instance.value(); currentInstance = inst; std::map propertyCategories; for (int i = 0; i <= PROPERTY_CATEGORY_MAX; i++) { QTreeWidgetItem* item = new QTreeWidgetItem; QBrush brush; brush.setColor(QPalette::Midlight); brush.setStyle(Qt::SolidPattern); item->setData(0, Qt::DisplayRole, PROPERTY_CATEGORY_NAMES[i]); item->setFirstColumnSpanned(true); propertyCategories[(PropertyCategory)i] = item; addTopLevelItem(item); } std::vector properties = inst->GetProperties(); for (std::string property : properties) { PropertyMeta meta = inst->GetPropertyMeta(property).value(); Data::Variant currentValue = inst->GetPropertyValue(property).value(); if (meta.type == &Data::CFrame::TYPE) continue; QTreeWidgetItem* item = new QTreeWidgetItem; item->setFlags(item->flags() | Qt::ItemIsEditable | Qt::ItemIsSelectable); item->setData(0, Qt::DisplayRole, QString::fromStdString(property)); if (meta.type == &Data::Bool::TYPE) { item->setCheckState(1, (bool)currentValue.get() ? Qt::CheckState::Checked : Qt::CheckState::Unchecked); } else if (meta.type == &Data::Color3::TYPE) { Data::Color3 color = currentValue.get(); item->setData(1, Qt::DecorationRole, QColor::fromRgbF(color.R(), color.G(), color.B())); item->setData(1, Qt::DisplayRole, QString::fromStdString(currentValue.ToString())); } else { item->setData(1, Qt::DisplayRole, QString::fromStdString(currentValue.ToString())); } if (meta.type != &Data::Color3::TYPE && (!meta.type->fromString || meta.flags & PROP_READONLY)) { item->setDisabled(true); } propertyCategories[meta.category]->addChild(item); propertyCategories[meta.category]->setExpanded(true); } // Remove child-less categories for (int i = 0; i <= PROPERTY_CATEGORY_MAX; i++) { if (propertyCategories[(PropertyCategory)i]->childCount() > 0) continue; int idx = indexOfTopLevelItem(propertyCategories[(PropertyCategory)i]); delete takeTopLevelItem(idx); } resizeColumnToContents(0); } void PropertiesView::propertyChanged(QTreeWidgetItem *item, int column) { if (!item->parent() || !currentInstance || currentInstance->expired()) return; InstanceRef inst = currentInstance->lock(); std::string propertyName = item->data(0, Qt::DisplayRole).toString().toStdString(); PropertyMeta meta = inst->GetPropertyMeta(propertyName).value(); if (meta.type == &Data::Bool::TYPE) { inst->SetPropertyValue(propertyName, Data::Bool(item->checkState(1))); } }