Physics Simulation & Visualization Tool 0.1
A C++ physics simulation engine with real-time 3D visualization
Loading...
Searching...
No Matches
HierarchyWidget.cpp
Go to the documentation of this file.
1#include "HierarchyWidget.h"
2
3#include <QVBoxLayout>
4#include <QMenu>
5#include <QAction>
6#include <QLabel>
7#include <QHeaderView>
8#include <QToolButton>
9
10#include "physics/PointMass.h"
11
12HierarchyWidget::HierarchyWidget(QWidget* parent) : QWidget(parent) {
13 tree = new QTreeWidget(this);
14 tree->setHeaderLabels({ "Name", "Type" });
15 QHeaderView* header = tree->header();
16 header->setSectionResizeMode(0, QHeaderView::Stretch);
17 header->setSectionResizeMode(1, QHeaderView::Stretch);
18 tree->setContextMenuPolicy(Qt::CustomContextMenu);
19 tree->setEditTriggers(QAbstractItemView::NoEditTriggers);
20 connect(tree, &QTreeWidget::itemDoubleClicked, this, [this](QTreeWidgetItem* item, int column) {
21 Q_UNUSED(column);
22 if (SceneObject* obj = getObjectFromItem(item))
23 emit focusObjectRequested(obj);
24 });
25 connect(tree, &QTreeWidget::itemChanged, this, &HierarchyWidget::onItemNameChanged);
26
27 QToolButton* addButton = new QToolButton(this);
28 addButton->setText("Add");
29 addButton->setAutoRaise(true);
30 addButton->setPopupMode(QToolButton::InstantPopup);
31
32 QMenu* addMenu = new QMenu(addButton);
33 QAction* addSceneObject = addMenu->addAction("Empty Scene Object");
34 connect(addSceneObject, &QAction::triggered, this, [this]() {
36 });
37 QAction* addPointMass = addMenu->addAction("Point Mass");
38 connect(addPointMass, &QAction::triggered, this, [this]() {
40 });
41 QAction* addRigidBody = addMenu->addAction("Rigid Body");
42 connect(addRigidBody, &QAction::triggered, this, [this]() {
43 emit createObjectRequested({RigidBodyOptions::Box({})}); // TODO: should be a sphere but I haven't implemented a sphere collider yet
44 });
45 addButton->setMenu(addMenu);
46
47 QHBoxLayout* headerLayout = new QHBoxLayout();
48 headerLayout->addWidget(addButton);
49 headerLayout->addStretch();
50 headerLayout->setContentsMargins(0, 0, 0, 0);
51 headerLayout->setSpacing(0);
52
53 QVBoxLayout* layout = new QVBoxLayout(this);
54 layout->addLayout(headerLayout);
55 layout->addWidget(tree);
56 layout->setContentsMargins(0, 0, 0, 0);
57 layout->setSpacing(0);
58
59 setLayout(layout);
60
61 connect(tree, &QTreeWidget::itemSelectionChanged, this, &HierarchyWidget::onItemSelectionChanged);
62 connect(tree, &QTreeWidget::customContextMenuRequested, this, &HierarchyWidget::showContextMenu);
63}
64
65void HierarchyWidget::showContextMenu(const QPoint& pos) {
66 QMenu contextMenu(this);
67
68 QAction* unselectAction = contextMenu.addAction("Deselect All");
69 connect(unselectAction, &QAction::triggered, [this]() {
70 tree->setCurrentItem(nullptr);
71 tree->clearSelection();
72 });
73 contextMenu.addSeparator();
74
75 if (SceneObject* obj = getObjectFromItem(tree->itemAt(pos))) {
76 QAction* focusAction = contextMenu.addAction("Focus Camera");
77 connect(focusAction, &QAction::triggered, [this, obj]() {
78 emit focusObjectRequested(obj);
79 });
80
81 QAction* followAction = contextMenu.addAction("Follow Camera");
82 connect(followAction, &QAction::triggered, [this, obj]() {
83 emit followObjectRequested(obj);
84 });
85
86 QAction* clearFollowAction = contextMenu.addAction("Stop Camera Follow");
87 connect(clearFollowAction, &QAction::triggered, [this]() {
89 });
90
91 QAction* renameAction = contextMenu.addAction("Rename");
92 connect(renameAction, &QAction::triggered, [this, pos]() {
93 if (QTreeWidgetItem* item = tree->itemAt(pos)) {
94 tree->editItem(item, 0);
95 }
96 });
97
98 contextMenu.addSeparator();
99 }
100
101 QAction* addPmAction = contextMenu.addAction("Add Point Mass");
102
103 // connect(addPmAction, &QAction::triggered, [this]() {
104 // emit createObjectRequested("Point Mass");
105 // });
106
107 contextMenu.exec(tree->mapToGlobal(pos));
108}
109
110QString HierarchyWidget::typeFor(SceneObject* obj) {
111 if (!obj->getPhysicsBody())
112 return "Scene Object";
113
114 if (dynamic_cast<Physics::PointMass*>(obj->getPhysicsBody()))
115 return "Point Mass";
116
117 if (dynamic_cast<Physics::RigidBody*>(obj->getPhysicsBody()))
118 return "Rigid Body";
119
120 return "Physics Object";
121}
122
124 auto* item = new QTreeWidgetItem();
125 item->setText(0, QString::fromStdString(obj->getName()));
126 item->setText(1, typeFor(obj));
127 item->setData(0, Qt::UserRole, QVariant::fromValue<void*>(obj));
128 item->setFlags(item->flags() | Qt::ItemIsEditable);
129 tree->addTopLevelItem(item);
130}
131
133 for (int i = 0; i < tree->topLevelItemCount(); ++i) {
134 QTreeWidgetItem* item = tree->topLevelItem(i);
135
136 if (getObjectFromItem(item) == obj) {
137 if (item == previousItem)
138 previousItem = nullptr;
139
140 delete tree->takeTopLevelItem(i);
141 break;
142 }
143 }
144}
145
147 if (!obj) {
148 tree->setCurrentItem(nullptr);
149 tree->clearSelection();
150 return;
151 }
152
153 for (int i = 0; i < tree->topLevelItemCount(); ++i) {
154 QTreeWidgetItem* item = tree->topLevelItem(i);
155 if (getObjectFromItem(item) == obj) {
156 tree->setCurrentItem(item); // will then call onItemSelectionChanged()
157 break;
158 }
159 }
160}
161
162void HierarchyWidget::onItemNameChanged(QTreeWidgetItem* item, int column) {
163 if (column != 0) return;
164
165 void* ptr = item->data(0, Qt::UserRole).value<void*>();
166 auto* obj = static_cast<SceneObject*>(ptr);
167
168 if (!obj) return;
169
170 emit renameObjectRequested(obj, item->text(0));
171}
172
173void HierarchyWidget::setObjectName(SceneObject *obj, const QString &name) {
174 QSignalBlocker blocker(tree);
175
176 for (int i = 0; i < tree->topLevelItemCount(); ++i) {
177 auto* item = tree->topLevelItem(i);
178 if (getObjectFromItem(item) == obj) {
179 item->setText(0, name);
180 return;
181 }
182 }
183}
184
186 SceneObject* previous = getObjectFromItem(previousItem);
187 SceneObject* current = getObjectFromItem(tree->currentItem());
188
189 emit selectionChanged(previous, current);
190
191 previousItem = tree->currentItem();
192}
193
194SceneObject *HierarchyWidget::getObjectFromItem(QTreeWidgetItem *item) {
195 if (!item) return nullptr;
196 QVariant var = item->data(0, Qt::UserRole);
197 return static_cast<SceneObject*>(var.value<void*>());
198}
199
200void HierarchyWidget::keyPressEvent(QKeyEvent* event) {
201 if (event->key() == Qt::Key_Delete || event->key() == Qt::Key_Backspace) {
203 }
204 QWidget::keyPressEvent(event);
205}
206
208 if (QTreeWidgetItem* item = tree->currentItem()) {
209 if (SceneObject* obj = getObjectFromItem(item)) {
210 emit deleteObjectRequested(obj);
211 }
212 }
213}
void renameObjectRequested(SceneObject *obj, const QString &newName)
void selectObject(SceneObject *obj)
void addObject(SceneObject *obj)
void removeObject(SceneObject *obj)
void setObjectName(SceneObject *obj, const QString &name)
void focusObjectRequested(SceneObject *obj)
void deleteObjectRequested(SceneObject *obj)
void clearCameraFollowRequested()
void showContextMenu(const QPoint &pos)
HierarchyWidget(QWidget *parent=nullptr)
void followObjectRequested(SceneObject *obj)
void selectionChanged(SceneObject *previous, SceneObject *current)
void onItemNameChanged(QTreeWidgetItem *item, int column)
void keyPressEvent(QKeyEvent *event) override
void createObjectRequested(const CreationOptions &options)
Physics::PhysicsBody * getPhysicsBody() const
Definition SceneObject.h:39
const std::string & getName() const
Definition SceneObject.h:48
static RigidBodyOptions Box(ObjectOptions base, bool isStatic=false, double mass=1.0, glm::vec3 velocity=glm::vec3(0.0f))