42 const std::string prefix_class =
"class ";
43 const std::string prefix_struct =
"struct ";
45 if (s.rfind(prefix_class, 0) == 0)
return s.substr(prefix_class.length());
46 if (s.rfind(prefix_struct, 0) == 0)
return s.substr(prefix_struct.length());
50 std::unique_ptr<char, void(*)(
void*)> res {
51 abi::__cxa_demangle(name, NULL, NULL, &status),
54 return (status == 0) ? res.get() : name;
103 std::pair<bool, vex::GameObject*>& selectedObject,
104 const std::unordered_map<
vex::Entity, std::vector<vex::GameObject*>>& childrenMap,
105 const std::unordered_set<vex::Entity>& runtimeSet,
107 const char* filter =
""
113 bool isRuntime = runtimeSet.find(entityID) != runtimeSet.end();
115 std::string name =
"Unnamed Object";
119 bool matchesFilter =
true;
120 if (filter && filter[0] !=
'\0') {
121 std::string lowerName = name;
122 std::string lowerFilter = filter;
123 std::transform(lowerName.begin(), lowerName.end(), lowerName.begin(), [](
unsigned char c){ return std::tolower(c); });
124 std::transform(lowerFilter.begin(), lowerFilter.end(), lowerFilter.begin(), [](
unsigned char c){ return std::tolower(c); });
125 if (lowerName.find(lowerFilter) == std::string::npos) {
126 matchesFilter =
false;
130 std::string label = name +
"##" + std::to_string((uint32_t)entityID);
132 ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_SpanAvailWidth;
133 if (filter && filter[0] !=
'\0' && matchesFilter) {
134 flags |= ImGuiTreeNodeFlags_DefaultOpen;
137 bool isSelected = (selectedObject.second == obj);
140 flags |= ImGuiTreeNodeFlags_Selected;
143 bool hasChildren = childrenMap.find(entityID) != childrenMap.end();
145 flags |= ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen;
148 bool shouldDraw = matchesFilter || (filter && filter[0] ==
'\0');
153 ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(1.00f, 0.23f, 0.01f, 0.30f));
154 ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(1.00f, 0.23f, 0.01f, 0.40f));
158 ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 59, 3, 255));
161 isOpen = ImGui::TreeNodeEx((
void*)(uint64_t)entityID, flags,
"%s", name.c_str());
166 if (shouldDraw && ImGui::BeginDragDropSource()) {
167 ImGui::SetDragDropPayload(
"SCENE_HIERARCHY_NODE", &obj,
sizeof(
vex::GameObject*));
169 ImGui::Text(
"Moving %s", name.c_str());
170 ImGui::EndDragDropSource();
173 if (shouldDraw && ImGui::BeginDragDropTarget()) {
174 if (
const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(
"SCENE_HIERARCHY_NODE")) {
177 outAction.type = SceneAction::REPARENT;
178 outAction.target = droppedObject;
179 outAction.newParent = obj;
181 ImGui::EndDragDropTarget();
186 ImGui::PopStyleColor();
190 ImGui::PopStyleColor(2);
193 if (ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen()) {
194 selectedObject.second = obj;
195 selectedObject.first = isRuntime;
198 if (ImGui::BeginPopupContextItem()) {
199 if (ImGui::MenuItem(
"Rename")) {
200 outAction.type = SceneAction::RENAME_START;
201 outAction.target = obj;
204 if (ImGui::MenuItem(
"Duplicate")) {
205 outAction.type = SceneAction::DUPLICATE;
206 outAction.target = obj;
213 if (ImGui::MenuItem(
"Unparent")) {
219 if (ImGui::MenuItem(
"Delete")) {
220 outAction.type = SceneAction::DELETE_ACTION;
221 outAction.target = obj;
227 if (ImGui::IsItemHovered()) {
229 const char* rawName =
typeid(*raw).name();
230 std::string className =
Demangle(rawName);
232 ImGui::BeginTooltip();
233 ImGui::TextColored(ImVec4(1.00f, 0.23f, 0.01f, 1.0f),
"Object Details");
235 ImGui::Text(
"C++ Class: %s", className.c_str());
236 ImGui::Text(
"Entity ID: %u", (uint32_t)entityID);
238 ImGui::TextColored(ImVec4(0.47f, 0.05f, 0.05f, 1.0f),
"Warning: Object created at runtime\n(Won't be saved)");
244 if (isOpen && hasChildren) {
245 const auto& children = childrenMap.at(entityID);
246 for (
auto* child : children) {
247 DrawEntityNode(child, selectedObject, childrenMap, runtimeSet, outAction, filter);
262 static char searchBuffer[256] =
"";
263 ImGui::InputTextWithHint(
"##SearchHierarchy",
"Search...", searchBuffer,
sizeof(searchBuffer));
271 if (objects.empty() && runtimeObjects.empty()) {
272 ImGui::TextDisabled(
"No objects in scene");
276 std::unordered_map<vex::Entity, std::vector<vex::GameObject*>> childrenMap;
277 std::vector<vex::GameObject*> rootNodes;
278 std::unordered_set<vex::Entity> runtimeSet;
280 auto processObjects = [&](
const auto& sourceList,
bool isRuntimeList) {
281 for (
const auto& objPtr : sourceList) {
282 if (!objPtr)
continue;
287 runtimeSet.insert(entity);
290 bool hasParent =
false;
296 childrenMap[parentID].push_back(obj);
302 rootNodes.push_back(obj);
307 processObjects(objects,
false);
308 processObjects(runtimeObjects,
true);
312 for (
auto* root : rootNodes) {
313 DrawEntityNode(root, selectedObject, childrenMap, runtimeSet, action, searchBuffer);
316 static bool showRenameModal =
false;
317 static char renameBuffer[128] =
"";
320 if (action.type == SceneAction::RENAME_START) {
321 objectToRename = action.target;
322 std::string currentName =
"Unnamed";
326 strncpy(renameBuffer, currentName.c_str(),
sizeof(renameBuffer));
327 showRenameModal =
true;
328 ImGui::OpenPopup(
"Rename Object");
331 if (ImGui::BeginPopupModal(
"Rename Object", NULL, ImGuiWindowFlags_AlwaysAutoResize)) {
332 if (objectToRename) {
333 ImGui::InputText(
"New Name", renameBuffer,
sizeof(renameBuffer));
335 if (ImGui::Button(
"Save") || ImGui::IsKeyPressed(ImGuiKey_Enter)) {
339 showRenameModal =
false;
340 ImGui::CloseCurrentPopup();
343 if (ImGui::Button(
"Cancel")) {
344 showRenameModal =
false;
345 ImGui::CloseCurrentPopup();
348 ImGui::CloseCurrentPopup();
353 if (action.type == SceneAction::DUPLICATE && action.target) {
358 std::string newName =
"Unnamed (Copy)";
366 const auto& regNames = vex::ComponentRegistry::getInstance().getRegisteredNames();
367 for (
const auto& compName : regNames) {
368 nlohmann::json compData = vex::ComponentRegistry::getInstance().saveComponent(*src, compName);
369 if (!compData.is_null()) {
370 vex::ComponentRegistry::getInstance().loadComponent(*newObj, compName, compData);
391 if (childrenMap.find(src->
GetEntity()) != childrenMap.end()) {
392 for (
auto* child : childrenMap.at(src->
GetEntity())) {
393 if (runtimeSet.find(child->GetEntity()) == runtimeSet.end()) {
394 recursiveCopy(child, newObj->
GetEntity());
400 std::string rootNewName =
"Unnamed";
405 std::string originalName = rootNewName;
419 if (action.type == SceneAction::DELETE_ACTION && action.target) {
426 if (childrenMap.find(targetObj->GetEntity()) != childrenMap.end()) {
427 auto children = childrenMap.at(targetObj->GetEntity());
428 for (
auto* child : children) {
429 recursiveDelete(child);
433 if (selectedObject.second == targetObj) {
434 selectedObject.first =
false;
435 selectedObject.second =
nullptr;
440 scene->DestroyGameObject(targetObj);
444 recursiveDelete(action.target);
447 if (action.type == SceneAction::REPARENT && action.target && action.newParent) {