Fix hierarchy rename field behavior

This commit is contained in:
2026-03-30 00:49:10 +08:00
parent 7aca8199be
commit 416aa72194
3 changed files with 101 additions and 45 deletions

View File

@@ -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;
}

View File

@@ -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 };

View File

@@ -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<XCEngine::Components::GameObject*>& 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<uint64_t, 256>& 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<int>(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();
}