diff --git a/editor/src/UI/StyleTokens.h b/editor/src/UI/StyleTokens.h index 5c88088a..d4f3e2d2 100644 --- a/editor/src/UI/StyleTokens.h +++ b/editor/src/UI/StyleTokens.h @@ -354,6 +354,18 @@ inline float InlineRenameFieldRounding() { return 2.0f; } +inline ImVec4 InlineRenameFieldBackgroundColor() { + return ImVec4(0.25f, 0.25f, 0.25f, 1.0f); +} + +inline ImVec4 InlineRenameFieldHoveredColor() { + return InlineRenameFieldBackgroundColor(); +} + +inline ImVec4 InlineRenameFieldActiveColor() { + return InlineRenameFieldBackgroundColor(); +} + inline float InlineRenameFieldHeight() { return ImGui::GetFontSize() + InlineRenameFieldFramePadding().y * 2.0f; } diff --git a/editor/src/UI/Widgets.h b/editor/src/UI/Widgets.h index d6ba39dc..2f56f9fa 100644 --- a/editor/src/UI/Widgets.h +++ b/editor/src/UI/Widgets.h @@ -210,6 +210,9 @@ inline InlineRenameFieldResult DrawInlineRenameField( ImGuiInputTextFlags flags = ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll) { ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, InlineRenameFieldFramePadding()); ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, InlineRenameFieldRounding()); + ImGui::PushStyleColor(ImGuiCol_FrameBg, InlineRenameFieldBackgroundColor()); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, InlineRenameFieldHoveredColor()); + ImGui::PushStyleColor(ImGuiCol_FrameBgActive, InlineRenameFieldActiveColor()); ImGui::SetNextItemWidth(width); if (requestFocus) { ImGui::SetKeyboardFocusHere(); @@ -219,6 +222,7 @@ inline InlineRenameFieldResult DrawInlineRenameField( const bool active = ImGui::IsItemActive(); const bool deactivated = ImGui::IsItemDeactivated(); const bool cancelRequested = active && ImGui::IsKeyPressed(ImGuiKey_Escape); + ImGui::PopStyleColor(3); ImGui::PopStyleVar(2); return InlineRenameFieldResult{ submitted, cancelRequested, deactivated, active }; diff --git a/editor/src/panels/HierarchyPanel.cpp b/editor/src/panels/HierarchyPanel.cpp index 32969d5f..de20622b 100644 --- a/editor/src/panels/HierarchyPanel.cpp +++ b/editor/src/panels/HierarchyPanel.cpp @@ -29,6 +29,65 @@ void DrawHierarchyTreePrefix(const XCEngine::Editor::UI::TreeNodePrefixContext& XCEngine::Editor::UI::AssetIconKind::GameObject); } +XCEngine::Editor::UI::TreeNodeDefinition BuildHierarchyNodeDefinition( + XCEngine::Editor::IEditorContext& context, + XCEngine::Components::GameObject* gameObject, + XCEngine::Editor::UI::TargetedPopupState& itemContextMenu) { + XCEngine::Editor::UI::TreeNodeDefinition nodeDefinition; + nodeDefinition.options.selected = context.GetSelectionManager().IsSelected(gameObject->GetID()); + nodeDefinition.options.leaf = gameObject->GetChildCount() == 0; + nodeDefinition.style = XCEngine::Editor::UI::HierarchyTreeStyle(); + nodeDefinition.prefix.width = XCEngine::Editor::UI::NavigationTreePrefixWidth(); + nodeDefinition.prefix.draw = DrawHierarchyTreePrefix; + nodeDefinition.callbacks.onInteraction = [&context, &itemContextMenu, gameObject](const XCEngine::Editor::UI::TreeNodeResult& node) { + if (node.clicked) { + XCEngine::Editor::Actions::HandleHierarchySelectionClick( + context, + gameObject->GetID(), + ImGui::GetIO().KeyCtrl); + } + + if (node.secondaryClicked) { + XCEngine::Editor::Actions::HandleHierarchyItemContextRequest( + context, + gameObject, + itemContextMenu); + } + }; + nodeDefinition.callbacks.onRenderExtras = [&context, gameObject]() { + XCEngine::Editor::Actions::BeginHierarchyEntityDrag(gameObject); + XCEngine::Editor::Actions::AcceptHierarchyEntityDrop(context, gameObject); + }; + return nodeDefinition; +} + +void DrawHierarchyRenameFieldAtCurrentRow( + XCEngine::Editor::UI::InlineTextEditState& renameState, + const XCEngine::Editor::UI::TreeViewStyle& style) { + const ImVec2 itemMin = ImGui::GetItemRectMin(); + const ImVec2 itemMax = ImGui::GetItemRectMax(); + const float arrowSlotWidth = ImGui::GetTreeNodeToLabelSpacing(); + const float prefixWidth = XCEngine::Editor::UI::NavigationTreePrefixWidth(); + const float prefixGap = XCEngine::Editor::UI::NavigationTreePrefixLabelGap(); + const float labelMinX = + itemMin.x + + arrowSlotWidth + + style.prefixStartOffset + + prefixWidth + + prefixGap; + const float renameWidth = (std::max)(0.0f, itemMax.x - labelMinX); + const float renameY = itemMin.y + + (std::max)(0.0f, (itemMax.y - itemMin.y - XCEngine::Editor::UI::InlineRenameFieldHeight()) * 0.5f); + + XCEngine::Editor::UI::DrawInlineRenameFieldAt( + "##Rename", + ImVec2(labelMinX, renameY), + renameState.Buffer(), + renameState.BufferSize(), + renameWidth, + renameState.ConsumeFocusRequest()); +} + } // namespace namespace XCEngine { @@ -134,56 +193,37 @@ void HierarchyPanel::RenderEntity(::XCEngine::Components::GameObject* gameObject ImGui::PushID(static_cast(gameObject->GetID())); - if (m_renameState.IsEditing(gameObject->GetID())) { - const UI::InlineRenameFieldResult renameField = UI::DrawInlineRenameField( - "##Rename", - m_renameState.Buffer(), - m_renameState.BufferSize(), - -1.0f, - m_renameState.ConsumeFocusRequest()); + UI::TreeNodeDefinition nodeDefinition = + BuildHierarchyNodeDefinition(*m_context, gameObject, m_itemContextMenu); + const std::string persistenceKey = std::to_string(gameObject->GetUUID()); + nodeDefinition.persistenceKey = persistenceKey; + const bool editing = m_renameState.IsEditing(gameObject->GetID()); + const UI::TreeNodeResult node = UI::DrawTreeNode( + &m_treeState, + (void*)gameObject->GetUUID(), + editing ? "" : gameObject->GetName().c_str(), + nodeDefinition); - if (renameField.cancelRequested) { + if (editing) { + DrawHierarchyRenameFieldAtCurrentRow(m_renameState, nodeDefinition.style); + + const bool active = ImGui::IsItemActive(); + const bool deactivated = ImGui::IsItemDeactivated(); + const bool cancelRequested = active && ImGui::IsKeyPressed(ImGuiKey_Escape); + if (cancelRequested) { CancelRename(); - } else if (renameField.submitted || renameField.deactivated) { + } else if (deactivated || ImGui::IsKeyPressed(ImGuiKey_Enter) || ImGui::IsKeyPressed(ImGuiKey_KeypadEnter)) { CommitRename(); } - } else { - UI::TreeNodeDefinition nodeDefinition; - nodeDefinition.options.selected = m_context->GetSelectionManager().IsSelected(gameObject->GetID()); - nodeDefinition.options.leaf = gameObject->GetChildCount() == 0; - const std::string persistenceKey = std::to_string(gameObject->GetUUID()); - nodeDefinition.persistenceKey = persistenceKey; - nodeDefinition.style = UI::HierarchyTreeStyle(); - nodeDefinition.prefix.width = UI::NavigationTreePrefixWidth(); - nodeDefinition.prefix.draw = DrawHierarchyTreePrefix; - nodeDefinition.callbacks.onInteraction = [this, gameObject](const UI::TreeNodeResult& node) { - if (node.clicked) { - Actions::HandleHierarchySelectionClick(*m_context, gameObject->GetID(), ImGui::GetIO().KeyCtrl); - } - - if (node.secondaryClicked) { - Actions::HandleHierarchyItemContextRequest(*m_context, gameObject, m_itemContextMenu); - } - }; - nodeDefinition.callbacks.onRenderExtras = [this, gameObject]() { - Actions::BeginHierarchyEntityDrag(gameObject); - Actions::AcceptHierarchyEntityDrop(*m_context, gameObject); - }; - - const UI::TreeNodeResult node = UI::DrawTreeNode( - &m_treeState, - (void*)gameObject->GetUUID(), - gameObject->GetName().c_str(), - nodeDefinition); - - if (node.open) { - for (size_t i = 0; i < gameObject->GetChildCount(); i++) { - RenderEntity(gameObject->GetChild(i)); - } - UI::EndTreeNode(); - } } - + + if (node.open) { + for (size_t i = 0; i < gameObject->GetChildCount(); i++) { + RenderEntity(gameObject->GetChild(i)); + } + UI::EndTreeNode(); + } + ImGui::PopID(); }