#include "scene_hierarchy_panel.hpp" #include #include #include #include void SceneHierarchyPanel::onUpdate(const sophia::FrameInfo&) { drawSceneSelector(); ImGui::Separator(); if (!context.hasScene()) { ImGui::TextDisabled("No scene open"); return; } sophia::Scene& scene = context.getScene(); sophia::Entity& selected = context.selected; if (ImGui::Button("+ Sprite")) { selected = scene.createEntity("Sprite"); selected.addComponent(); } ImGui::SameLine(); if (ImGui::Button("+ Cube")) { selected = scene.createEntity("Cube"); selected.addComponent( sophia::MeshComponent{.handle = cubeMesh}); } ImGui::Separator(); // Flat for now: there is no parent/child component yet. scene.forEachEntity([&selected](sophia::Entity entity) { ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen | ImGuiTreeNodeFlags_SpanAvailWidth; if (entity == selected) { flags |= ImGuiTreeNodeFlags_Selected; } const std::string& name = entity.getComponent().tag; ImGui::PushID(static_cast(static_cast(entity))); ImGui::TreeNodeEx(name.c_str(), flags); if (ImGui::IsItemClicked()) { selected = entity; } ImGui::PopID(); }); } namespace { // See SceneHierarchyPanel::renameFocusFrames. constexpr int RENAME_FOCUS_FRAMES = 2; } // namespace void SceneHierarchyPanel::drawSceneSelector() { // Leave room for the button: its label, its padding, and the gap to it. constexpr const char* ADD_SCENE_LABEL = "Add Scene"; const ImGuiStyle& style = ImGui::GetStyle(); ImGui::SetNextItemWidth(-(ImGui::CalcTextSize(ADD_SCENE_LABEL).x + style.FramePadding.x * 2.f + style.ItemSpacing.x)); const std::string* current = context.getSceneName(context.getSceneHandle()); bool startRename = false; if (renamingScene && current) { drawSceneRename(); } else { renamingScene = false; if (ImGui::BeginCombo("##scene", current ? current->c_str() : "No scene")) { for (const EditorContext::OpenScene& open : context.getOpenScenes()) { ImGui::PushID(static_cast(open.handle.index)); bool isCurrent = open.handle == context.getSceneHandle(); if (ImGui::Selectable(open.name.c_str(), isCurrent) && !isCurrent) { context.setScene(open.handle); } ImGui::PopID(); } ImGui::EndCombo(); } if (current && ImGui::BeginPopupContextItem("##sceneMenu")) { startRename = ImGui::MenuItem("Rename", "F2"); ImGui::EndPopup(); } startRename = startRename || (current && ImGui::IsWindowFocused() && !ImGui::GetIO().WantTextInput && ImGui::IsKeyPressed(ImGuiKey_F2)); } ImGui::SameLine(); if (ImGui::Button(ADD_SCENE_LABEL)) { std::string name = "Untitled " + std::to_string(++this->untitledSceneCount); context.setScene(context.createScene(std::move(name))); startRename = false; } if (startRename) { renamingScene = true; renameFocusFrames = RENAME_FOCUS_FRAMES; std::snprintf(renameBuffer.data(), renameBuffer.size(), "%s", current->c_str()); } } void SceneHierarchyPanel::drawSceneRename() { if (renameFocusFrames == RENAME_FOCUS_FRAMES) { ImGui::SetKeyboardFocusHere(); } ImGui::InputText("##sceneName", renameBuffer.data(), renameBuffer.size(), ImGuiInputTextFlags_AutoSelectAll); bool active = ImGui::IsItemActive(); if (active) { renameFocusFrames = 0; } else if (renameFocusFrames > 0) { renameFocusFrames--; return; } // Escape reverts the text before deactivating, so committing whatever is // left when the field closes covers Enter, clicking away and Escape alike. if (!active) { std::string name = renameBuffer.data(); // Keep the old name rather than leave the scene nameless. if (name.find_first_not_of(' ') != std::string::npos) { context.renameScene(context.getSceneHandle(), std::move(name)); } renamingScene = false; } }