feat(editor): new properties widget
This commit is contained in:
parent
d972f98ea4
commit
e8ca7e8a9e
8 changed files with 153 additions and 164 deletions
|
@ -49,12 +49,23 @@ enum PropertyFlags {
|
|||
PROP_NOSAVE = 1 << 1, // Do not serialize
|
||||
};
|
||||
|
||||
enum PropertyCategory {
|
||||
PROP_CATEGORY_DATA,
|
||||
PROP_CATEGORY_APPEARENCE,
|
||||
PROP_CATEGORY_BEHAVIOR,
|
||||
PROP_CATEGORY_PART,
|
||||
PROP_CATEGORY_SURFACE,
|
||||
};
|
||||
|
||||
const int PROPERTY_CATEGORY_MAX = PROP_CATEGORY_SURFACE;
|
||||
|
||||
struct PropertyMeta {
|
||||
void* backingField;
|
||||
const Data::TypeInfo* type;
|
||||
FieldCodec codec;
|
||||
std::optional<std::function<void(std::string name)>> updateCallback;
|
||||
PropertyFlags flags;
|
||||
PropertyCategory category = PROP_CATEGORY_DATA;
|
||||
};
|
||||
|
||||
typedef std::variant<PropertyMeta> MemberMeta;
|
||||
|
|
|
@ -70,11 +70,13 @@ Part::Part(PartConstructParams params): Instance(&TYPE), cframe(Data::CFrame(par
|
|||
.backingField = &anchored,
|
||||
.type = &Data::Bool::TYPE,
|
||||
.codec = fieldCodecOf<Data::Bool, bool>(),
|
||||
.updateCallback = memberFunctionOf(&Part::onUpdated, this)
|
||||
.updateCallback = memberFunctionOf(&Part::onUpdated, this),
|
||||
.category = PROP_CATEGORY_BEHAVIOR,
|
||||
}}, { "Locked", {
|
||||
.backingField = &locked,
|
||||
.type = &Data::Bool::TYPE,
|
||||
.codec = fieldCodecOf<Data::Bool, bool>(),
|
||||
.category = PROP_CATEGORY_BEHAVIOR,
|
||||
}}, { "Position", {
|
||||
.backingField = &cframe,
|
||||
.type = &Vector3::TYPE,
|
||||
|
@ -96,15 +98,18 @@ Part::Part(PartConstructParams params): Instance(&TYPE), cframe(Data::CFrame(par
|
|||
.backingField = &size,
|
||||
.type = &Vector3::TYPE,
|
||||
.codec = fieldCodecOf<Vector3, glm::vec3>(),
|
||||
.updateCallback = memberFunctionOf(&Part::onUpdated, this)
|
||||
.updateCallback = memberFunctionOf(&Part::onUpdated, this),
|
||||
.category = PROP_CATEGORY_PART,
|
||||
}}, { "Color", {
|
||||
.backingField = &color,
|
||||
.type = &Data::Color3::TYPE,
|
||||
.codec = fieldCodecOf<Data::Color3>(),
|
||||
.category = PROP_CATEGORY_APPEARENCE,
|
||||
}}, { "Transparency", {
|
||||
.backingField = &transparency,
|
||||
.type = &Data::Float::TYPE,
|
||||
.codec = fieldCodecOf<Data::Float, float>(),
|
||||
.category = PROP_CATEGORY_APPEARENCE,
|
||||
}}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -30,8 +30,6 @@ set(PROJECT_SOURCES
|
|||
panes/explorermodel.cpp
|
||||
panes/propertiesview.h
|
||||
panes/propertiesview.cpp
|
||||
panes/propertiesmodel.h
|
||||
panes/propertiesmodel.cpp
|
||||
${TS_FILES}
|
||||
)
|
||||
|
||||
|
@ -64,7 +62,7 @@ else()
|
|||
endif()
|
||||
|
||||
target_include_directories(editor PUBLIC "../core/src" "../include")
|
||||
target_link_libraries(editor PRIVATE openblocks Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Multimedia)
|
||||
target_link_libraries(editor PRIVATE openblocks Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::WidgetsPrivate Qt${QT_VERSION_MAJOR}::Multimedia)
|
||||
|
||||
# Qt6 does not include QOpenGLWidgets as part of Widgets base anymore, so
|
||||
# we have to include it manually
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>935</width>
|
||||
<width>1027</width>
|
||||
<height>30</height>
|
||||
</rect>
|
||||
</property>
|
||||
|
|
|
@ -1,118 +0,0 @@
|
|||
#include "propertiesmodel.h"
|
||||
#include "datatypes/base.h"
|
||||
#include "datatypes/cframe.h"
|
||||
#include "objects/base/member.h"
|
||||
#include "qnamespace.h"
|
||||
|
||||
PropertiesModel::PropertiesModel(InstanceRef selectedItem, QWidget *parent)
|
||||
: QAbstractItemModel(parent)
|
||||
, selectedItem(selectedItem) {
|
||||
this->propertiesList.reserve(selectedItem->GetProperties().size());
|
||||
for (std::string name : selectedItem->GetProperties()) {
|
||||
PropertyMeta meta = selectedItem->GetPropertyMeta(name).value();
|
||||
// Don't show CFrames in properties
|
||||
if (meta.type == &Data::CFrame::TYPE) continue;
|
||||
|
||||
this->propertiesList.push_back(name);
|
||||
}
|
||||
}
|
||||
|
||||
PropertiesModel::~PropertiesModel() = default;
|
||||
|
||||
|
||||
QVariant PropertiesModel::data(const QModelIndex &index, int role) const {
|
||||
if (!index.isValid())
|
||||
return {};
|
||||
|
||||
std::string propertyName = propertiesList[index.row()];
|
||||
PropertyMeta meta = selectedItem->GetPropertyMeta(propertyName).value();
|
||||
|
||||
switch (role) {
|
||||
case Qt::EditRole:
|
||||
case Qt::DisplayRole:
|
||||
if (index.column() == 0)
|
||||
return QString::fromStdString(propertyName);
|
||||
else if (index.column() == 1 && meta.type != &Data::Bool::TYPE) {
|
||||
return QString::fromStdString(selectedItem->GetPropertyValue(propertyName).value().ToString());
|
||||
}
|
||||
return {};
|
||||
case Qt::CheckStateRole:
|
||||
if (index.column() == 0) return {};
|
||||
else if (index.column() == 1 && meta.type == &Data::Bool::TYPE)
|
||||
return selectedItem->GetPropertyValue(propertyName)->get<Data::Bool>() ? Qt::Checked : Qt::Unchecked;
|
||||
return {};
|
||||
// case Qt::DecorationRole:
|
||||
// return iconOf(item->GetClass());
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
bool PropertiesModel::setData(const QModelIndex &index, const QVariant &value, int role) {
|
||||
if (index.column() != 1) return false;
|
||||
|
||||
std::string propertyName = propertiesList[index.row()];
|
||||
PropertyMeta meta = selectedItem->GetPropertyMeta(propertyName).value();
|
||||
|
||||
switch (role) {
|
||||
case Qt::EditRole:
|
||||
if (!meta.type->fromString)
|
||||
return false;
|
||||
|
||||
selectedItem->SetPropertyValue(propertyName, meta.type->fromString(value.toString().toStdString()));
|
||||
return true;
|
||||
case Qt::CheckStateRole:
|
||||
if (meta.type != &Data::Bool::TYPE)
|
||||
return false;
|
||||
|
||||
selectedItem->SetPropertyValue(propertyName, Data::Bool(value.toBool()));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Qt::ItemFlags PropertiesModel::flags(const QModelIndex &index) const {
|
||||
if (!index.isValid())
|
||||
return Qt::NoItemFlags;
|
||||
|
||||
if (index.column() == 0)
|
||||
return Qt::ItemIsEnabled;
|
||||
|
||||
std::string propertyName = propertiesList[index.row()];
|
||||
PropertyMeta meta = selectedItem->GetPropertyMeta(propertyName).value();
|
||||
|
||||
if (index.column() == 1) {
|
||||
if (meta.type == &Data::Bool::TYPE)
|
||||
return Qt::ItemIsEnabled | Qt::ItemIsUserCheckable;
|
||||
else
|
||||
return Qt::ItemIsEnabled | Qt::ItemIsEditable;
|
||||
}
|
||||
|
||||
return Qt::NoItemFlags;
|
||||
};
|
||||
|
||||
QVariant PropertiesModel::headerData(int section, Qt::Orientation orientation,
|
||||
int role) const {
|
||||
return QString("");
|
||||
}
|
||||
|
||||
QModelIndex PropertiesModel::index(int row, int column,
|
||||
const QModelIndex &parent) const {
|
||||
if (!hasIndex(row, column, parent))
|
||||
return {};
|
||||
|
||||
return createIndex(row, column);
|
||||
}
|
||||
|
||||
QModelIndex PropertiesModel::parent(const QModelIndex &index) const {
|
||||
return {};
|
||||
}
|
||||
|
||||
int PropertiesModel::rowCount(const QModelIndex &parent) const {
|
||||
return !parent.isValid() ? this->propertiesList.size() : 0;
|
||||
}
|
||||
|
||||
int PropertiesModel::columnCount(const QModelIndex &parent) const {
|
||||
return 2;
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "objects/base/instance.h"
|
||||
#include "qabstractitemmodel.h"
|
||||
#include "qnamespace.h"
|
||||
#include <QOpenGLWidget>
|
||||
#include <QWidget>
|
||||
|
||||
class PropertiesModel : public QAbstractItemModel {
|
||||
Q_OBJECT
|
||||
public:
|
||||
Q_DISABLE_COPY_MOVE(PropertiesModel)
|
||||
|
||||
explicit PropertiesModel(InstanceRef selectedItem, QWidget *parent = nullptr);
|
||||
~PropertiesModel() override;
|
||||
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
bool setData(const QModelIndex &index, const QVariant &value, int role) override;
|
||||
Qt::ItemFlags flags(const QModelIndex &index) const override;
|
||||
QVariant headerData(int section, Qt::Orientation orientation,
|
||||
int role = Qt::DisplayRole) const override;
|
||||
QModelIndex index(int row, int column,
|
||||
const QModelIndex &parent = {}) const override;
|
||||
QModelIndex parent(const QModelIndex &index) const override;
|
||||
int rowCount(const QModelIndex &parent = {}) const override;
|
||||
int columnCount(const QModelIndex &parent = {}) const override;
|
||||
|
||||
private:
|
||||
InstanceRef selectedItem;
|
||||
std::vector<std::string> propertiesList;
|
||||
};
|
|
@ -1,20 +1,141 @@
|
|||
#include "propertiesview.h"
|
||||
#include "datatypes/meta.h"
|
||||
#include "objects/base/member.h"
|
||||
#include "propertiesmodel.h"
|
||||
#include "qaction.h"
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <qabstractitemdelegate.h>
|
||||
#include <qbrush.h>
|
||||
#include <qnamespace.h>
|
||||
#include <qpalette.h>
|
||||
#include <qstyle.h>
|
||||
#include <qstyleditemdelegate.h>
|
||||
#include <qstyleoption.h>
|
||||
#include <qtreewidget.h>
|
||||
#include <QDebug>
|
||||
#include <QStyledItemDelegate>
|
||||
#include <private/qtreeview_p.h>
|
||||
|
||||
class CustomItemDelegate : public QStyledItemDelegate {
|
||||
public:
|
||||
CustomItemDelegate(QObject* parent = nullptr) : 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<PropertiesView*>(parent())->indentation();
|
||||
|
||||
QStyledItemDelegate::initStyleOption(option, index);
|
||||
|
||||
if (index.parent().isValid()) {
|
||||
option->rect.adjust(-indent, 0, -indent, 0);
|
||||
} else {
|
||||
option->state &= ~QStyle::State_Selected;
|
||||
|
||||
QWidget* parentWidget = dynamic_cast<QWidget*>(parent());
|
||||
option->backgroundBrush = parentWidget->palette().dark();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
PropertiesView::PropertiesView(QWidget* parent):
|
||||
QTreeView(parent) {
|
||||
this->setStyleSheet(QString("QTreeView::branch { border: none; }"));
|
||||
QTreeWidget(parent) {
|
||||
|
||||
clear();
|
||||
setHeaderHidden(true);
|
||||
setColumnCount(2);
|
||||
setAlternatingRowColors(true);
|
||||
setItemDelegate(new CustomItemDelegate(this));
|
||||
}
|
||||
|
||||
PropertiesView::~PropertiesView() {
|
||||
}
|
||||
|
||||
QStringList PROPERTY_CATEGORY_NAMES {
|
||||
"Data",
|
||||
"Appearence",
|
||||
"Behavior",
|
||||
"Part",
|
||||
"Surface"
|
||||
};
|
||||
|
||||
void PropertiesView::drawBranches(QPainter *painter, const QRect &rect, const QModelIndex &index) const {
|
||||
// https://codebrowser.dev/qt5/qtbase/src/widgets/itemviews/qtreeview.cpp.html#312opt
|
||||
Q_D(const QTreeView);
|
||||
QStyleOptionViewItem opt = viewOptions();
|
||||
|
||||
const QTreeViewItem& viewItem = d->viewItems.at(d->current);
|
||||
|
||||
// Taken from source code (above)
|
||||
bool hoverRow = selectionBehavior() == QAbstractItemView::SelectRows
|
||||
&& opt.showDecorationSelected
|
||||
&& index.parent() == d->hover.parent()
|
||||
&& index.row() == d->hover.row();
|
||||
|
||||
// Un-indent branch
|
||||
opt.rect = rect;
|
||||
if (index.parent().isValid())
|
||||
opt.rect.adjust(0, 0, -indentation(), 0);
|
||||
opt.state |= QStyle::State_Item;
|
||||
if (viewItem.hasChildren)
|
||||
opt.state |= QStyle::State_Children;
|
||||
if (viewItem.expanded)
|
||||
opt.state |= QStyle::State_Open;
|
||||
if (viewItem.hasMoreSiblings || viewItem.parentItem > -1 && d->viewItems.at(viewItem.parentItem).hasMoreSiblings)
|
||||
opt.state |= QStyle::State_Sibling;
|
||||
|
||||
opt.state.setFlag(QStyle::State_MouseOver, hoverRow || d->current == d->hoverBranch);
|
||||
|
||||
// Draw background for headings
|
||||
if (!index.parent().isValid())
|
||||
painter->fillRect(opt.rect, palette().dark());
|
||||
|
||||
style()->drawPrimitive(QStyle::PE_IndicatorBranch, &opt, painter, this);
|
||||
}
|
||||
|
||||
void PropertiesView::setSelected(std::optional<InstanceRef> instance) {
|
||||
if (instance.has_value()) {
|
||||
this->setModel(new PropertiesModel(instance.value()));
|
||||
} else {
|
||||
if (this->model()) delete this->model();
|
||||
this->setModel(nullptr);
|
||||
clear();
|
||||
if (!instance) return;
|
||||
InstanceRef inst = instance.value();
|
||||
|
||||
std::map<PropertyCategory, QTreeWidgetItem*> 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<std::string> properties = inst->GetProperties();
|
||||
|
||||
for (std::string property : properties) {
|
||||
PropertyMeta meta = inst->GetPropertyMeta(property).value();
|
||||
Data::Variant currentValue = inst->GetPropertyValue(property).value();
|
||||
|
||||
QTreeWidgetItem* item = new QTreeWidgetItem;
|
||||
item->setData(0, Qt::DisplayRole, QString::fromStdString(property));
|
||||
item->setData(1, Qt::DisplayRole, QString::fromStdString(currentValue.ToString()));
|
||||
|
||||
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);
|
||||
}
|
|
@ -1,11 +1,14 @@
|
|||
#pragma once
|
||||
|
||||
#include <QTreeWidget>
|
||||
#include "objects/base/instance.h"
|
||||
#include "qtreeview.h"
|
||||
|
||||
class Ui_MainWindow;
|
||||
|
||||
class PropertiesView : public QTreeView {
|
||||
class PropertiesView : public QTreeWidget {
|
||||
Q_DECLARE_PRIVATE(QTreeView)
|
||||
protected:
|
||||
void drawBranches(QPainter *painter, const QRect &rect, const QModelIndex &index) const override;
|
||||
public:
|
||||
PropertiesView(QWidget* parent = nullptr);
|
||||
~PropertiesView() override;
|
||||
|
|
Loading…
Add table
Reference in a new issue