From c1e7a0d49fc23cf98b103b861820650566ab6ee7 Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Wed, 22 Apr 2026 18:37:05 +0800 Subject: [PATCH] new_editor: remove tree panel behavior layer --- ...sidualDuplicationClosurePlan_2026-04-22.md | 121 +++++++++++++ ...undancyRefactorPlan_完成归档_2026-04-22.md | 79 ++++++++ new_editor/CMakeLists.txt | 1 - .../app/Features/Hierarchy/HierarchyPanel.cpp | 120 +++---------- .../app/Features/Project/ProjectPanel.cpp | 170 ++++++------------ .../Collections/UIEditorTreeDragDrop.h | 49 ++++- .../Collections/UIEditorTreePanelBehavior.h | 52 ------ .../XCEditor/Collections/UIEditorTreeView.h | 52 ++++++ .../Collections/UIEditorTreeViewInteraction.h | 13 ++ .../Collections/UIEditorTreePanelBehavior.cpp | 134 -------------- .../src/Collections/UIEditorTreeView.cpp | 149 +++++++++++++++ .../UIEditorTreeViewInteraction.cpp | 25 +++ 12 files changed, 567 insertions(+), 398 deletions(-) create mode 100644 docs/plan/NewEditor_TreeResidualDuplicationClosurePlan_2026-04-22.md create mode 100644 docs/used/NewEditor_TreePanelHostRedundancyRefactorPlan_完成归档_2026-04-22.md delete mode 100644 new_editor/include/XCEditor/Collections/UIEditorTreePanelBehavior.h delete mode 100644 new_editor/src/Collections/UIEditorTreePanelBehavior.cpp diff --git a/docs/plan/NewEditor_TreeResidualDuplicationClosurePlan_2026-04-22.md b/docs/plan/NewEditor_TreeResidualDuplicationClosurePlan_2026-04-22.md new file mode 100644 index 00000000..4015eb38 --- /dev/null +++ b/docs/plan/NewEditor_TreeResidualDuplicationClosurePlan_2026-04-22.md @@ -0,0 +1,121 @@ +# NewEditor Tree Residual Duplication Closure Plan + +Date: `2026-04-22` +Status: `Active` + +## Goal + +- Close the true remaining duplication between `HierarchyPanel` and the project left tree after the `TreePanelHost` / `UIEditorTreePanelBehavior` cleanup. +- Keep the solution rooted in existing modules instead of creating new `Behavior` / `Host` / `Support` files. +- Reduce code only where the duplication is structurally real, not where product semantics differ. + +## Why A New Plan Is Needed + +- The previous plan completed the removal of the tree glue layer. +- That did not automatically mean the hierarchy tree and project tree were free of all true duplication. +- The remaining work is smaller in scope, but it is real and should be tracked separately instead of being hidden under the completed root plan. + +## Confirmed Residual Duplication + +### 1. Hosted panel command focus claiming is still duplicated + +Current duplicated shape: + +- `HierarchyPanel::ClaimCommandFocus(...)` +- `ProjectPanel::ClaimCommandFocus(...)` +- `InspectorPanel::ClaimCommandFocus(...)` + +This is app-layer hosted panel behavior, not tree-business behavior. + +### 2. Tree rename host skeleton is still partially duplicated + +Current duplicated shape: + +- `ClearRenameState(...)` +- `QueueRenameSession(...)` +- `TryStartQueuedRenameSession(...)` +- `UpdateRenameSession(...)` + +Notes: + +- `HierarchyPanel` has a single tree surface. +- `ProjectPanel` has both tree and grid rename surfaces. +- The commit side-effects must stay local, but the session host skeleton still contains real duplication. + +### 3. Tree append/update skeleton must be re-audited carefully + +- Some call order is shared: tree background, tree foreground, inline rename overlay, drop preview. +- But `ProjectPanel` also owns splitter, breadcrumb, grid, context menu, asset drag/drop, and dual-surface rename. +- This area must not be “abstracted for symmetry” unless the extracted part is truly stable and reduces code clearly. + +## Explicit Non-Goals + +- Do not merge panel refresh loops. +- Do not merge project tree and asset grid logic. +- Do not move project or scene runtime semantics into `XCEditor`. +- Do not create any new `Behavior`, `Host`, `Helper`, `Support`, or similar glue files. +- Do not refactor for visual symmetry when code ownership would become less clear. + +## Execution Plan + +### Phase A. Hosted Panel Command Focus Dedup + +Target: + +- Consolidate hosted-panel command focus claiming into an existing app-level module. + +Rules: + +- No new file. +- Prefer extending an existing app-level focus module. +- Update `HierarchyPanel`, `ProjectPanel`, and `InspectorPanel` together if they share the same pattern. + +Validation: + +- Build `XCUIEditorApp` +- Smoke test `XCUIEditor.exe` + +### Phase B. Tree Rename Host Skeleton Dedup + +Target: + +- Reduce duplicated tree rename session host code while preserving local business commit behavior. + +Rules: + +- No new glue layer. +- Keep tree-generic session mechanics in existing tree/editor modules. +- Keep `ProjectPanel` grid rename semantics local. +- Keep `HierarchyPanel` scene rename commit local. + +Validation: + +- Build `XCUIEditorApp` +- Smoke test `XCUIEditor.exe` +- Confirm hierarchy rename and project tree/grid rename still start and shut down correctly + +### Phase C. Final Re-Audit Of Tree Skeleton Duplication + +Target: + +- Re-check whether any remaining shared tree call sequence should still be extracted. + +Rules: + +- Only extract if it removes clear structural duplication. +- If the remaining overlap is just normal orchestration around different business surfaces, record it as intentional and stop. + +Validation: + +- `rg` verification of targeted duplicate blocks +- Build `XCUIEditorApp` +- Smoke test `XCUIEditor.exe` + +## Acceptance + +- Previous root plan is archived. +- Remaining true duplication is tracked separately. +- Hosted panel command focus duplicate is removed from the duplicated panels. +- Tree rename host skeleton duplication is reduced without breaking project grid/tree split semantics. +- No new glue-layer file is introduced. +- `XCUIEditorApp` builds and smoke tests pass after each completed phase. diff --git a/docs/used/NewEditor_TreePanelHostRedundancyRefactorPlan_完成归档_2026-04-22.md b/docs/used/NewEditor_TreePanelHostRedundancyRefactorPlan_完成归档_2026-04-22.md new file mode 100644 index 00000000..ffa8f226 --- /dev/null +++ b/docs/used/NewEditor_TreePanelHostRedundancyRefactorPlan_完成归档_2026-04-22.md @@ -0,0 +1,79 @@ +# NewEditor Tree Root Refactor Plan + +Date: `2026-04-22` +Status: `Completed` + +## Goal + +- Remove `TreePanelHost` / `UIEditorTreePanelBehavior` style glue layers from `new_editor`. +- Keep tree shared logic inside existing `XCEditor` tree modules instead of app-level host wrappers. +- Keep `HierarchyPanel` and `ProjectPanel` focused on scene/project specific behavior only. +- Preserve existing performance and dispatch boundaries. + +## Root Cause + +- Shared tree code was split across app panels and thin glue files. +- Those glue files did not represent a stable domain module. They only forwarded tree input, rename, draw, and drag helpers. +- That produced extra file count, repeated filtering helpers, and fuzzy ownership. + +## Final Module Boundary + +- `UIEditorTreeView.*` + - tree layout + - tree rendering + - inline rename bounds/metrics/palette helpers + - tree drop preview helpers +- `UIEditorTreeViewInteraction.*` + - hosted tree input filtering + - tree interaction update +- `UIEditorTreeDragDrop.h` + - drag preview filtering + - pointer suppression filtering for tree drag lanes + - drag/drop processing +- `HierarchyPanel.cpp` + - scene selection + - hierarchy rename commit + - hierarchy reparent commit + - hierarchy event emission +- `ProjectPanel.cpp` + - project layout + - breadcrumb/grid/context menu behavior + - folder and asset rename commit + - project drag/drop commit + +## Explicit Non-Goals + +- Do not merge panel refresh loops. +- Do not change viewport request/render ownership. +- Do not change `EditorWindowFrameDriver` single frame owner semantics. +- Do not change hosted panel dispatch architecture. +- Do not move scene/project runtime semantics into `XCEditor`. + +## Execution + +1. Removed the old app-side `TreePanelHost` path. +2. Moved durable inline rename and drop-preview helpers into `UIEditorTreeView.*`. +3. Moved hosted tree input filtering into `UIEditorTreeViewInteraction.*`. +4. Moved tree pointer suppression filtering into `UIEditorTreeDragDrop.h`. +5. Replaced remaining `HierarchyPanel` and `ProjectPanel` calls to `UIEditorTreePanelBehavior` with direct tree-module calls. +6. Deleted `new_editor/include/XCEditor/Collections/UIEditorTreePanelBehavior.h`. +7. Deleted `new_editor/src/Collections/UIEditorTreePanelBehavior.cpp`. +8. Removed `src/Collections/UIEditorTreePanelBehavior.cpp` from `new_editor/CMakeLists.txt`. + +## Acceptance Result + +- `UIEditorTreePanelBehavior.*` no longer exists. +- Tree shared logic now lives in existing tree modules, not new helper layers. +- Tree hosted input filtering is bottomed out and reusable instead of duplicated in each panel. +- Panel code keeps only business-specific logic and no longer owns thin tree glue. + +## Verification + +- Build command: + - `cmake --build build --config Debug --target XCUIEditorApp -- /m:1` +- Smoke test: + - launched `build/new_editor/Debug/XCUIEditor.exe` + - confirmed `runtime.log` contains: + - `EnsureEditorStartupScene loaded scene=Main Scene` + - `initialize end` + - `shutdown end` diff --git a/new_editor/CMakeLists.txt b/new_editor/CMakeLists.txt index 46d53a39..eb8dc103 100644 --- a/new_editor/CMakeLists.txt +++ b/new_editor/CMakeLists.txt @@ -67,7 +67,6 @@ set(XCUI_EDITOR_COLLECTION_SOURCES src/Collections/UIEditorListViewInteraction.cpp src/Collections/UIEditorScrollView.cpp src/Collections/UIEditorScrollViewInteraction.cpp - src/Collections/UIEditorTreePanelBehavior.cpp src/Collections/UIEditorTabStrip.cpp src/Collections/UIEditorTabStripInteraction.cpp src/Collections/UIEditorTreeView.cpp diff --git a/new_editor/app/Features/Hierarchy/HierarchyPanel.cpp b/new_editor/app/Features/Hierarchy/HierarchyPanel.cpp index ae22116a..c58c3d7b 100644 --- a/new_editor/app/Features/Hierarchy/HierarchyPanel.cpp +++ b/new_editor/app/Features/Hierarchy/HierarchyPanel.cpp @@ -3,7 +3,6 @@ #include #include "Scene/EditorSceneRuntime.h" #include "State/EditorCommandFocusService.h" -#include #include #include #include @@ -53,10 +52,6 @@ UIEditorHostCommandDispatchResult BuildDispatchResult( return result; } -bool HasValidBounds(const UIRect& bounds) { - return bounds.width > 0.0f && bounds.height > 0.0f; -} - bool ContainsPoint(const UIRect& rect, const UIPoint& point) { return point.x >= rect.x && point.x <= rect.x + rect.width && @@ -238,34 +233,17 @@ bool HierarchyPanel::TryStartQueuedRenameSession( } const UIRect bounds = BuildRenameBounds(m_pendingRenameItemId, layout); - if (!HasValidBounds(bounds)) { + if (!TryStartUIEditorTreeViewInlineRenameSession( + m_renameState, + m_renameFrame, + m_pendingRenameItemId, + node->label, + bounds)) { return false; } - const Widgets::UIEditorTextFieldMetrics textFieldMetrics = - BuildUIEditorInlineRenameTextFieldMetrics( - bounds, - BuildUIEditorPropertyGridTextFieldMetrics( - ResolveUIEditorPropertyGridMetrics(), - ResolveUIEditorTextFieldMetrics())); - - UIEditorInlineRenameSessionRequest request = {}; - request.beginSession = true; - request.itemId = m_pendingRenameItemId; - request.initialText = node->label; - request.bounds = bounds; - m_renameFrame = UpdateUIEditorInlineRenameSession( - m_renameState, - request, - {}, - textFieldMetrics); - - if (m_renameFrame.result.sessionStarted) { - m_pendingRenameItemId.clear(); - return true; - } - - return false; + m_pendingRenameItemId.clear(); + return true; } void HierarchyPanel::UpdateRenameSession( @@ -281,27 +259,16 @@ void HierarchyPanel::UpdateRenameSession( } const UIRect bounds = BuildRenameBounds(m_renameState.itemId, layout); - if (!HasValidBounds(bounds)) { + if (!HasUIEditorTreeViewValidBounds(bounds)) { ClearRenameState(); return; } - const Widgets::UIEditorTextFieldMetrics textFieldMetrics = - BuildUIEditorInlineRenameTextFieldMetrics( - bounds, - BuildUIEditorPropertyGridTextFieldMetrics( - ResolveUIEditorPropertyGridMetrics(), - ResolveUIEditorTextFieldMetrics())); - - UIEditorInlineRenameSessionRequest request = {}; - request.itemId = m_renameState.itemId; - request.initialText = m_renameState.textFieldSpec.value; - request.bounds = bounds; - m_renameFrame = UpdateUIEditorInlineRenameSession( + UpdateUIEditorTreeViewInlineRenameSession( m_renameState, - request, - inputEvents, - textFieldMetrics); + m_renameFrame, + bounds, + inputEvents); if (!m_renameFrame.result.sessionCommitted) { return; @@ -357,15 +324,11 @@ void HierarchyPanel::ClaimCommandFocus( UIRect HierarchyPanel::BuildRenameBounds( std::string_view itemId, const Widgets::UIEditorTreeViewLayout& layout) const { - const Widgets::UIEditorTextFieldMetrics hostedMetrics = - BuildUIEditorPropertyGridTextFieldMetrics( - ResolveUIEditorPropertyGridMetrics(), - ResolveUIEditorTextFieldMetrics()); - return BuildUIEditorTreePanelInlineRenameBounds( + return BuildUIEditorTreeViewInlineRenameBounds( layout, m_treeItems, itemId, - hostedMetrics); + ResolveUIEditorTreeViewHostedTextFieldMetrics()); } bool HierarchyPanel::HasActivePointerCapture() const { @@ -478,10 +441,10 @@ void HierarchyPanel::ProcessDragAndFrameEvents( const std::vector& inputEvents, const UIRect& bounds, const UIEditorHostedPanelDispatchEntry& dispatchEntry) { - const std::vector filteredEvents = BuildUIEditorTreePanelInputEvents( + const std::vector filteredEvents = BuildUIEditorHostedTreeViewInputEvents( bounds, inputEvents, - UIEditorTreePanelInputFilterOptions{ + UIEditorHostedTreeViewInputOptions{ .allowInteraction = dispatchEntry.allowInteraction, .hasInputFocus = dispatchEntry.focused, .captureActive = HasActivePointerCapture() @@ -580,10 +543,10 @@ void HierarchyPanel::Update( } m_visible = true; - const std::vector filteredEvents = BuildUIEditorTreePanelInputEvents( + const std::vector filteredEvents = BuildUIEditorHostedTreeViewInputEvents( dispatchEntry.bounds, inputEvents, - UIEditorTreePanelInputFilterOptions{ + UIEditorHostedTreeViewInputOptions{ .allowInteraction = dispatchEntry.allowInteraction, .hasInputFocus = dispatchEntry.focused, .captureActive = HasActivePointerCapture() @@ -615,12 +578,11 @@ void HierarchyPanel::Update( } const std::vector interactionEvents = - BuildUIEditorTreePanelInteractionInputEvents( + TreeDrag::BuildInteractionInputEvents( m_dragState, layout, m_treeItems, filteredEvents, - false, kDragThreshold); m_treeFrame = UpdateUIEditorTreeViewInteraction( m_treeInteractionState, @@ -669,48 +631,22 @@ void HierarchyPanel::Append(UIDrawList& drawList) const { metrics); if (m_renameState.active) { - const Widgets::UIEditorTextFieldPalette textFieldPalette = - BuildUIEditorPropertyGridTextFieldPalette( - ResolveUIEditorPropertyGridPalette(), - ResolveUIEditorTextFieldPalette()); - const Widgets::UIEditorTextFieldMetrics textFieldMetrics = - BuildUIEditorInlineRenameTextFieldMetrics( - BuildRenameBounds(m_renameState.itemId, m_treeFrame.layout), - BuildUIEditorPropertyGridTextFieldMetrics( - ResolveUIEditorPropertyGridMetrics(), - ResolveUIEditorTextFieldMetrics())); AppendUIEditorInlineRenameSession( drawList, m_renameFrame, m_renameState, - textFieldPalette, - textFieldMetrics); + ResolveUIEditorTreeViewInlineRenamePalette(), + BuildUIEditorTreeViewInlineRenameMetrics( + BuildRenameBounds(m_renameState.itemId, m_treeFrame.layout))); } - if (!m_dragState.dragging || !m_dragState.validDropTarget) { - return; - } - - if (m_dragState.dropToRoot) { - drawList.AddRectOutline( - m_treeFrame.layout.bounds, - kDragPreviewColor, - 1.0f, - 0.0f); - return; - } - - const std::size_t visibleIndex = FindUIEditorTreePanelVisibleItemIndex( + AppendUIEditorTreeViewDropPreview( + drawList, m_treeFrame.layout, m_treeItems, - m_dragState.dropTargetItemId); - if (visibleIndex == Widgets::UIEditorTreeViewInvalidIndex || - visibleIndex >= m_treeFrame.layout.rowRects.size()) { - return; - } - - drawList.AddRectOutline( - m_treeFrame.layout.rowRects[visibleIndex], + m_dragState.dragging && m_dragState.validDropTarget, + m_dragState.dropToRoot, + m_dragState.dropTargetItemId, kDragPreviewColor, 1.0f, 0.0f); diff --git a/new_editor/app/Features/Project/ProjectPanel.cpp b/new_editor/app/Features/Project/ProjectPanel.cpp index eb14ce13..1106370c 100644 --- a/new_editor/app/Features/Project/ProjectPanel.cpp +++ b/new_editor/app/Features/Project/ProjectPanel.cpp @@ -8,7 +8,6 @@ #include "Ports/SystemInteractionPort.h" #include "Project/EditorProjectRuntime.h" #include "State/EditorCommandFocusService.h" -#include #include #include #include @@ -478,12 +477,10 @@ UIRect ProjectPanel::BuildRenameBounds( } const Widgets::UIEditorTextFieldMetrics hostedMetrics = - BuildUIEditorPropertyGridTextFieldMetrics( - ResolveUIEditorPropertyGridMetrics(), - ResolveUIEditorTextFieldMetrics()); + ResolveUIEditorTreeViewHostedTextFieldMetrics(); if (surface == RenameSurface::Tree) { - return BuildUIEditorTreePanelInlineRenameBounds( + return BuildUIEditorTreeViewInlineRenameBounds( m_treeFrame.layout, GetWindowTreeItems(), itemId, @@ -541,28 +538,12 @@ bool ProjectPanel::TryStartQueuedRenameSession() { const UIRect bounds = BuildRenameBounds(m_pendingRenameItemId, m_pendingRenameSurface); - if (!HasValidBounds(bounds)) { - return false; - } - - const Widgets::UIEditorTextFieldMetrics textFieldMetrics = - BuildUIEditorInlineRenameTextFieldMetrics( - bounds, - BuildUIEditorPropertyGridTextFieldMetrics( - ResolveUIEditorPropertyGridMetrics(), - ResolveUIEditorTextFieldMetrics())); - - UIEditorInlineRenameSessionRequest request = {}; - request.beginSession = true; - request.itemId = m_pendingRenameItemId; - request.initialText = initialText; - request.bounds = bounds; - m_renameFrame = UpdateUIEditorInlineRenameSession( - m_renameState, - request, - {}, - textFieldMetrics); - if (!m_renameFrame.result.sessionStarted) { + if (!TryStartUIEditorTreeViewInlineRenameSession( + m_renameState, + m_renameFrame, + m_pendingRenameItemId, + std::move(initialText), + bounds)) { return false; } @@ -579,27 +560,16 @@ void ProjectPanel::UpdateRenameSession( } const UIRect bounds = BuildRenameBounds(m_renameState.itemId, m_activeRenameSurface); - if (!HasValidBounds(bounds)) { + if (!HasUIEditorTreeViewValidBounds(bounds)) { ClearRenameState(); return; } - const Widgets::UIEditorTextFieldMetrics textFieldMetrics = - BuildUIEditorInlineRenameTextFieldMetrics( - bounds, - BuildUIEditorPropertyGridTextFieldMetrics( - ResolveUIEditorPropertyGridMetrics(), - ResolveUIEditorTextFieldMetrics())); - - UIEditorInlineRenameSessionRequest request = {}; - request.itemId = m_renameState.itemId; - request.initialText = m_renameState.textFieldSpec.value; - request.bounds = bounds; - m_renameFrame = UpdateUIEditorInlineRenameSession( + UpdateUIEditorTreeViewInlineRenameSession( m_renameState, - request, - inputEvents, - textFieldMetrics); + m_renameFrame, + bounds, + inputEvents); if (!m_renameFrame.result.sessionCommitted) { if (m_renameFrame.result.sessionCanceled) { m_activeRenameSurface = RenameSurface::None; @@ -1066,20 +1036,13 @@ std::vector ProjectPanel::BuildTreeInteractionInputEvents( const UIRect& bounds, const UIEditorHostedPanelDispatchEntry& dispatchEntry) const { const std::vector rawEvents = - BuildUIEditorPanelInputEvents( + BuildUIEditorHostedTreeViewInputEvents( bounds, inputEvents, - UIEditorPanelInputFilterOptions{ - .allowPointerInBounds = dispatchEntry.allowInteraction, - .allowPointerWhileCaptured = HasActivePointerCapture(), - .allowKeyboardInput = dispatchEntry.focused, - .allowFocusEvents = - dispatchEntry.focused || - HasActivePointerCapture() || - dispatchEntry.focusGained || - dispatchEntry.focusLost, - .includePointerLeave = - dispatchEntry.allowInteraction || HasActivePointerCapture() + UIEditorHostedTreeViewInputOptions{ + .allowInteraction = dispatchEntry.allowInteraction, + .hasInputFocus = dispatchEntry.focused, + .captureActive = HasActivePointerCapture() }, dispatchEntry.focusGained, dispatchEntry.focusLost); @@ -1093,11 +1056,12 @@ std::vector ProjectPanel::BuildTreeInteractionInputEvents( m_folderExpansion, ResolveUIEditorTreeViewMetrics(), m_treeInteractionState.verticalOffset); - return BuildUIEditorTreePanelInteractionInputEvents( + return TreeDrag::BuildInteractionInputEvents( m_treeDragState, layout, GetWindowTreeItems(), rawEvents, + TreeDrag::kDefaultDragThreshold, m_splitterDragging || m_assetDragState.dragging); } @@ -1645,11 +1609,11 @@ void ProjectPanel::Update( m_treeDragState, m_treeFrame.layout, GetWindowTreeItems(), - FilterUIEditorTreePanelPointerInputEvents( - filteredEvents, - m_splitterDragging || m_assetDragState.dragging), + filteredEvents, m_layout.treeRect, - treeDragCallbacks); + treeDragCallbacks, + TreeDrag::kDefaultDragThreshold, + m_splitterDragging || m_assetDragState.dragging); if (treeDragResult.dropCommitted) { const bool hadAssetSelection = ResolveProjectRuntime()->HasSelection(); CloseContextMenu(); @@ -2206,45 +2170,29 @@ void ProjectPanel::Append(UIDrawList& drawList) const { treePalette, treeMetrics); - if (m_treeDragState.dragging && m_treeDragState.validDropTarget) { - if (m_treeDragState.dropToRoot) { - drawList.AddRectOutline( - m_treeFrame.layout.bounds, - kDropPreviewColor, - 1.0f, - 0.0f); - } else { - const std::size_t visibleIndex = FindUIEditorTreePanelVisibleItemIndex( - m_treeFrame.layout, - GetWindowTreeItems(), - m_treeDragState.dropTargetItemId); - if (visibleIndex != Widgets::UIEditorTreeViewInvalidIndex && - visibleIndex < m_treeFrame.layout.rowRects.size()) { - drawList.AddRectOutline( - m_treeFrame.layout.rowRects[visibleIndex], - kDropPreviewColor, - 1.0f, - 0.0f); - } - } - } + AppendUIEditorTreeViewDropPreview( + drawList, + m_treeFrame.layout, + GetWindowTreeItems(), + m_treeDragState.dragging && m_treeDragState.validDropTarget, + m_treeDragState.dropToRoot, + m_treeDragState.dropTargetItemId, + kDropPreviewColor, + 1.0f, + 0.0f); - if (m_assetDragState.dragging && - m_assetDragState.validDropTarget && - m_assetDropTargetSurface == DropTargetSurface::Tree) { - const std::size_t visibleIndex = FindUIEditorTreePanelVisibleItemIndex( - m_treeFrame.layout, - GetWindowTreeItems(), - m_assetDragState.dropTargetItemId); - if (visibleIndex != Widgets::UIEditorTreeViewInvalidIndex && - visibleIndex < m_treeFrame.layout.rowRects.size()) { - drawList.AddRectOutline( - m_treeFrame.layout.rowRects[visibleIndex], - kDropPreviewColor, - 1.0f, - 0.0f); - } - } + AppendUIEditorTreeViewDropPreview( + drawList, + m_treeFrame.layout, + GetWindowTreeItems(), + m_assetDragState.dragging && + m_assetDragState.validDropTarget && + m_assetDropTargetSurface == DropTargetSurface::Tree, + false, + m_assetDragState.dropTargetItemId, + kDropPreviewColor, + 1.0f, + 0.0f); drawList.PushClipRect(m_layout.browserHeaderRect); for (std::size_t index = 0u; index < m_layout.breadcrumbItems.size(); ++index) { @@ -2334,22 +2282,16 @@ void ProjectPanel::Append(UIDrawList& drawList) const { } if (m_renameState.active) { - const Widgets::UIEditorTextFieldPalette textFieldPalette = - BuildUIEditorPropertyGridTextFieldPalette( - ResolveUIEditorPropertyGridPalette(), - ResolveUIEditorTextFieldPalette()); - const Widgets::UIEditorTextFieldMetrics textFieldMetrics = - BuildUIEditorInlineRenameTextFieldMetrics( - BuildRenameBounds(m_renameState.itemId, m_activeRenameSurface), - BuildUIEditorPropertyGridTextFieldMetrics( - ResolveUIEditorPropertyGridMetrics(), - ResolveUIEditorTextFieldMetrics())); - AppendUIEditorInlineRenameSession( - drawList, - m_renameFrame, - m_renameState, - textFieldPalette, - textFieldMetrics); + const UIRect renameBounds = + BuildRenameBounds(m_renameState.itemId, m_activeRenameSurface); + if (HasUIEditorTreeViewValidBounds(renameBounds)) { + AppendUIEditorInlineRenameSession( + drawList, + m_renameFrame, + m_renameState, + ResolveUIEditorTreeViewInlineRenamePalette(), + BuildUIEditorTreeViewInlineRenameMetrics(renameBounds)); + } } if (assetEntries.empty()) { diff --git a/new_editor/include/XCEditor/Collections/UIEditorTreeDragDrop.h b/new_editor/include/XCEditor/Collections/UIEditorTreeDragDrop.h index ada20cae..a1e72cc0 100644 --- a/new_editor/include/XCEditor/Collections/UIEditorTreeDragDrop.h +++ b/new_editor/include/XCEditor/Collections/UIEditorTreeDragDrop.h @@ -73,12 +73,46 @@ inline std::size_t FindVisibleIndexForItemId( return UIEditorTreeViewInvalidIndex; } +inline bool IsSuppressedPointerInputEvent( + const DragDropInteraction::UIInputEvent& event) { + switch (event.type) { + case ::XCEngine::UI::UIInputEventType::PointerMove: + case ::XCEngine::UI::UIInputEventType::PointerButtonDown: + case ::XCEngine::UI::UIInputEventType::PointerButtonUp: + case ::XCEngine::UI::UIInputEventType::PointerWheel: + case ::XCEngine::UI::UIInputEventType::PointerEnter: + return true; + + default: + return false; + } +} + +inline std::vector FilterPointerInputEvents( + const std::vector& inputEvents, + bool suppressPointerInput) { + if (!suppressPointerInput) { + return inputEvents; + } + + std::vector filteredEvents = {}; + filteredEvents.reserve(inputEvents.size()); + for (const DragDropInteraction::UIInputEvent& event : inputEvents) { + if (!IsSuppressedPointerInputEvent(event)) { + filteredEvents.push_back(event); + } + } + + return filteredEvents; +} + inline std::vector BuildInteractionInputEvents( const State& state, const Widgets::UIEditorTreeViewLayout& layout, const std::vector& items, const std::vector& rawEvents, - float dragThreshold = kDefaultDragThreshold) { + float dragThreshold = kDefaultDragThreshold, + bool suppressPointerInput = false) { auto resolveDraggableItem = [&layout, &items](const UIPoint& point) -> std::string { UIEditorTreeViewHitTarget hitTarget = {}; @@ -94,9 +128,11 @@ inline std::vector BuildInteractionInputEvent DragDropInteraction::PreviewState preview = DragDropInteraction::BuildPreviewState(state); + const std::vector inputEvents = + FilterPointerInputEvents(rawEvents, suppressPointerInput); std::vector filteredEvents = {}; - filteredEvents.reserve(rawEvents.size()); - for (const DragDropInteraction::UIInputEvent& event : rawEvents) { + filteredEvents.reserve(inputEvents.size()); + for (const DragDropInteraction::UIInputEvent& event : inputEvents) { if (!DragDropInteraction::ProcessPreviewEvent( preview, event, @@ -117,7 +153,8 @@ ProcessResult ProcessInputEvents( const std::vector& inputEvents, const UIRect& bounds, Callbacks& callbacks, - float dragThreshold = kDefaultDragThreshold) { + float dragThreshold = kDefaultDragThreshold, + bool suppressPointerInput = false) { struct AdaptedCallbacks { const Widgets::UIEditorTreeViewLayout& layout; const std::vector& items; @@ -184,10 +221,12 @@ ProcessResult ProcessInputEvents( } adaptedCallbacks{ layout, items, bounds, callbacks }; ProcessResult result = {}; + const std::vector filteredInputEvents = + FilterPointerInputEvents(inputEvents, suppressPointerInput); const DragDropInteraction::ProcessResult interactionResult = DragDropInteraction::ProcessInputEvents( state, - inputEvents, + filteredInputEvents, adaptedCallbacks, dragThreshold); result.selectionForced = interactionResult.selectionForced; diff --git a/new_editor/include/XCEditor/Collections/UIEditorTreePanelBehavior.h b/new_editor/include/XCEditor/Collections/UIEditorTreePanelBehavior.h deleted file mode 100644 index 117bed7d..00000000 --- a/new_editor/include/XCEditor/Collections/UIEditorTreePanelBehavior.h +++ /dev/null @@ -1,52 +0,0 @@ -#pragma once - -#include -#include - -#include - -#include -#include -#include - -namespace XCEngine::UI::Editor { - -struct UIEditorTreePanelInputFilterOptions { - bool allowInteraction = false; - bool hasInputFocus = false; - bool captureActive = false; -}; - -std::vector<::XCEngine::UI::UIInputEvent> BuildUIEditorTreePanelInputEvents( - const ::XCEngine::UI::UIRect& bounds, - const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, - const UIEditorTreePanelInputFilterOptions& options, - bool synthesizeFocusGained = false, - bool synthesizeFocusLost = false); - -std::vector<::XCEngine::UI::UIInputEvent> FilterUIEditorTreePanelPointerInputEvents( - const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, - bool suppressPointerInput); - -std::vector<::XCEngine::UI::UIInputEvent> BuildUIEditorTreePanelInteractionInputEvents( - const Collections::TreeDragDrop::State& dragState, - const Widgets::UIEditorTreeViewLayout& layout, - const std::vector& items, - const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, - bool suppressPointerInput = false, - float dragThreshold = Collections::TreeDragDrop::kDefaultDragThreshold); - -std::size_t FindUIEditorTreePanelVisibleItemIndex( - const Widgets::UIEditorTreeViewLayout& layout, - const std::vector& items, - std::string_view itemId); - -::XCEngine::UI::UIRect BuildUIEditorTreePanelInlineRenameBounds( - const Widgets::UIEditorTreeViewLayout& layout, - const std::vector& items, - std::string_view itemId, - const Widgets::UIEditorTextFieldMetrics& hostedMetrics, - float minWidth = 120.0f, - float trailingPadding = 8.0f); - -} // namespace XCEngine::UI::Editor diff --git a/new_editor/include/XCEditor/Collections/UIEditorTreeView.h b/new_editor/include/XCEditor/Collections/UIEditorTreeView.h index cbbc492c..f49e44f2 100644 --- a/new_editor/include/XCEditor/Collections/UIEditorTreeView.h +++ b/new_editor/include/XCEditor/Collections/UIEditorTreeView.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -160,3 +161,54 @@ void AppendUIEditorTreeView( const UIEditorTreeViewMetrics& metrics = {}); } // namespace XCEngine::UI::Editor::Widgets + +namespace XCEngine::UI::Editor { + +bool HasUIEditorTreeViewValidBounds( + const ::XCEngine::UI::UIRect& bounds); + +std::size_t FindUIEditorTreeViewVisibleItemIndex( + const Widgets::UIEditorTreeViewLayout& layout, + const std::vector& items, + std::string_view itemId); + +::XCEngine::UI::UIRect BuildUIEditorTreeViewInlineRenameBounds( + const Widgets::UIEditorTreeViewLayout& layout, + const std::vector& items, + std::string_view itemId, + const Widgets::UIEditorTextFieldMetrics& hostedMetrics, + float minWidth = 120.0f, + float trailingPadding = 8.0f); + +Widgets::UIEditorTextFieldMetrics ResolveUIEditorTreeViewHostedTextFieldMetrics(); + +Widgets::UIEditorTextFieldMetrics BuildUIEditorTreeViewInlineRenameMetrics( + const ::XCEngine::UI::UIRect& bounds); + +Widgets::UIEditorTextFieldPalette ResolveUIEditorTreeViewInlineRenamePalette(); + +bool TryStartUIEditorTreeViewInlineRenameSession( + UIEditorInlineRenameSessionState& renameState, + UIEditorInlineRenameSessionFrame& renameFrame, + std::string_view itemId, + std::string initialText, + const ::XCEngine::UI::UIRect& bounds); + +void UpdateUIEditorTreeViewInlineRenameSession( + UIEditorInlineRenameSessionState& renameState, + UIEditorInlineRenameSessionFrame& renameFrame, + const ::XCEngine::UI::UIRect& bounds, + const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents); + +void AppendUIEditorTreeViewDropPreview( + ::XCEngine::UI::UIDrawList& drawList, + const Widgets::UIEditorTreeViewLayout& layout, + const std::vector& items, + bool active, + bool dropToRoot, + std::string_view dropTargetItemId, + const ::XCEngine::UI::UIColor& previewColor, + float borderThickness = 1.0f, + float cornerRounding = 0.0f); + +} // namespace XCEngine::UI::Editor diff --git a/new_editor/include/XCEditor/Collections/UIEditorTreeViewInteraction.h b/new_editor/include/XCEditor/Collections/UIEditorTreeViewInteraction.h index 5005c45e..d423c9f8 100644 --- a/new_editor/include/XCEditor/Collections/UIEditorTreeViewInteraction.h +++ b/new_editor/include/XCEditor/Collections/UIEditorTreeViewInteraction.h @@ -13,6 +13,12 @@ namespace XCEngine::UI::Editor { +struct UIEditorHostedTreeViewInputOptions { + bool allowInteraction = false; + bool hasInputFocus = false; + bool captureActive = false; +}; + struct UIEditorTreeViewInteractionState { Widgets::UIEditorTreeViewState treeViewState = {}; UIEditorScrollViewInteractionState scrollViewInteractionState = {}; @@ -42,6 +48,13 @@ struct UIEditorTreeViewInteractionFrame { UIEditorTreeViewInteractionResult result = {}; }; +std::vector<::XCEngine::UI::UIInputEvent> BuildUIEditorHostedTreeViewInputEvents( + const ::XCEngine::UI::UIRect& bounds, + const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, + const UIEditorHostedTreeViewInputOptions& options, + bool synthesizeFocusGained = false, + bool synthesizeFocusLost = false); + UIEditorTreeViewInteractionFrame UpdateUIEditorTreeViewInteraction( UIEditorTreeViewInteractionState& state, ::XCEngine::UI::Widgets::UISelectionModel& selectionModel, diff --git a/new_editor/src/Collections/UIEditorTreePanelBehavior.cpp b/new_editor/src/Collections/UIEditorTreePanelBehavior.cpp deleted file mode 100644 index 92202cda..00000000 --- a/new_editor/src/Collections/UIEditorTreePanelBehavior.cpp +++ /dev/null @@ -1,134 +0,0 @@ -#include -#include - -#include - -#include - -namespace XCEngine::UI::Editor { - -namespace { - -using ::XCEngine::UI::UIInputEvent; -using ::XCEngine::UI::UIInputEventType; -using ::XCEngine::UI::UIPoint; -using ::XCEngine::UI::UIRect; - -bool ContainsPoint(const UIRect& rect, const UIPoint& point) { - return point.x >= rect.x && - point.x <= rect.x + rect.width && - point.y >= rect.y && - point.y <= rect.y + rect.height; -} - -} // namespace - -std::vector BuildUIEditorTreePanelInputEvents( - const UIRect& bounds, - const std::vector& inputEvents, - const UIEditorTreePanelInputFilterOptions& options, - bool synthesizeFocusGained, - bool synthesizeFocusLost) { - return BuildUIEditorPanelInputEvents( - bounds, - inputEvents, - UIEditorPanelInputFilterOptions{ - .allowPointerInBounds = options.allowInteraction, - .allowPointerWhileCaptured = options.captureActive, - .allowKeyboardInput = options.hasInputFocus, - .allowFocusEvents = - options.hasInputFocus || - options.captureActive || - synthesizeFocusGained || - synthesizeFocusLost, - .includePointerLeave = options.allowInteraction || options.captureActive - }, - synthesizeFocusGained, - synthesizeFocusLost); -} - -std::vector FilterUIEditorTreePanelPointerInputEvents( - const std::vector& inputEvents, - bool suppressPointerInput) { - if (!suppressPointerInput) { - return inputEvents; - } - - std::vector filteredEvents = {}; - filteredEvents.reserve(inputEvents.size()); - for (const UIInputEvent& event : inputEvents) { - switch (event.type) { - case UIInputEventType::PointerMove: - case UIInputEventType::PointerButtonDown: - case UIInputEventType::PointerButtonUp: - case UIInputEventType::PointerWheel: - case UIInputEventType::PointerEnter: - break; - - default: - filteredEvents.push_back(event); - break; - } - } - - return filteredEvents; -} - -std::vector BuildUIEditorTreePanelInteractionInputEvents( - const Collections::TreeDragDrop::State& dragState, - const Widgets::UIEditorTreeViewLayout& layout, - const std::vector& items, - const std::vector& inputEvents, - bool suppressPointerInput, - float dragThreshold) { - return Collections::TreeDragDrop::BuildInteractionInputEvents( - dragState, - layout, - items, - FilterUIEditorTreePanelPointerInputEvents(inputEvents, suppressPointerInput), - dragThreshold); -} - -std::size_t FindUIEditorTreePanelVisibleItemIndex( - const Widgets::UIEditorTreeViewLayout& layout, - const std::vector& items, - std::string_view itemId) { - for (std::size_t visibleIndex = 0u; visibleIndex < layout.visibleItemIndices.size(); - ++visibleIndex) { - const std::size_t itemIndex = layout.visibleItemIndices[visibleIndex]; - if (itemIndex < items.size() && items[itemIndex].itemId == itemId) { - return visibleIndex; - } - } - - return Widgets::UIEditorTreeViewInvalidIndex; -} - -UIRect BuildUIEditorTreePanelInlineRenameBounds( - const Widgets::UIEditorTreeViewLayout& layout, - const std::vector& items, - std::string_view itemId, - const Widgets::UIEditorTextFieldMetrics& hostedMetrics, - float minWidth, - float trailingPadding) { - if (itemId.empty()) { - return {}; - } - - const std::size_t visibleIndex = - FindUIEditorTreePanelVisibleItemIndex(layout, items, itemId); - if (visibleIndex == Widgets::UIEditorTreeViewInvalidIndex || - visibleIndex >= layout.rowRects.size() || - visibleIndex >= layout.labelRects.size()) { - return {}; - } - - const UIRect& rowRect = layout.rowRects[visibleIndex]; - const UIRect& labelRect = layout.labelRects[visibleIndex]; - const float x = (std::max)(rowRect.x, labelRect.x - hostedMetrics.valueTextInsetX); - const float right = rowRect.x + rowRect.width - trailingPadding; - const float width = (std::max)(minWidth, right - x); - return UIRect(x, rowRect.y, width, rowRect.height); -} - -} // namespace XCEngine::UI::Editor diff --git a/new_editor/src/Collections/UIEditorTreeView.cpp b/new_editor/src/Collections/UIEditorTreeView.cpp index ac5c7639..c941661d 100644 --- a/new_editor/src/Collections/UIEditorTreeView.cpp +++ b/new_editor/src/Collections/UIEditorTreeView.cpp @@ -1,4 +1,6 @@ #include +#include +#include #include @@ -400,3 +402,150 @@ void AppendUIEditorTreeView( } } // namespace XCEngine::UI::Editor::Widgets + +namespace XCEngine::UI::Editor { + +using ::XCEngine::UI::UIRect; + +bool HasUIEditorTreeViewValidBounds(const UIRect& bounds) { + return bounds.width > 0.0f && bounds.height > 0.0f; +} + +std::size_t FindUIEditorTreeViewVisibleItemIndex( + const Widgets::UIEditorTreeViewLayout& layout, + const std::vector& items, + std::string_view itemId) { + for (std::size_t visibleIndex = 0u; visibleIndex < layout.visibleItemIndices.size(); + ++visibleIndex) { + const std::size_t itemIndex = layout.visibleItemIndices[visibleIndex]; + if (itemIndex < items.size() && items[itemIndex].itemId == itemId) { + return visibleIndex; + } + } + + return Widgets::UIEditorTreeViewInvalidIndex; +} + +UIRect BuildUIEditorTreeViewInlineRenameBounds( + const Widgets::UIEditorTreeViewLayout& layout, + const std::vector& items, + std::string_view itemId, + const Widgets::UIEditorTextFieldMetrics& hostedMetrics, + float minWidth, + float trailingPadding) { + if (itemId.empty()) { + return {}; + } + + const std::size_t visibleIndex = + FindUIEditorTreeViewVisibleItemIndex(layout, items, itemId); + if (visibleIndex == Widgets::UIEditorTreeViewInvalidIndex || + visibleIndex >= layout.rowRects.size() || + visibleIndex >= layout.labelRects.size()) { + return {}; + } + + const UIRect& rowRect = layout.rowRects[visibleIndex]; + const UIRect& labelRect = layout.labelRects[visibleIndex]; + const float x = (std::max)(rowRect.x, labelRect.x - hostedMetrics.valueTextInsetX); + const float right = rowRect.x + rowRect.width - trailingPadding; + const float width = (std::max)(minWidth, right - x); + return UIRect(x, rowRect.y, width, rowRect.height); +} + +Widgets::UIEditorTextFieldMetrics ResolveUIEditorTreeViewHostedTextFieldMetrics() { + return BuildUIEditorPropertyGridTextFieldMetrics( + ResolveUIEditorPropertyGridMetrics(), + ResolveUIEditorTextFieldMetrics()); +} + +Widgets::UIEditorTextFieldMetrics BuildUIEditorTreeViewInlineRenameMetrics( + const UIRect& bounds) { + return BuildUIEditorInlineRenameTextFieldMetrics( + bounds, + ResolveUIEditorTreeViewHostedTextFieldMetrics()); +} + +Widgets::UIEditorTextFieldPalette ResolveUIEditorTreeViewInlineRenamePalette() { + return BuildUIEditorPropertyGridTextFieldPalette( + ResolveUIEditorPropertyGridPalette(), + ResolveUIEditorTextFieldPalette()); +} + +bool TryStartUIEditorTreeViewInlineRenameSession( + UIEditorInlineRenameSessionState& renameState, + UIEditorInlineRenameSessionFrame& renameFrame, + std::string_view itemId, + std::string initialText, + const UIRect& bounds) { + if (!HasUIEditorTreeViewValidBounds(bounds)) { + return false; + } + + UIEditorInlineRenameSessionRequest request = {}; + request.beginSession = true; + request.itemId = std::string(itemId); + request.initialText = std::move(initialText); + request.bounds = bounds; + renameFrame = UpdateUIEditorInlineRenameSession( + renameState, + request, + {}, + BuildUIEditorTreeViewInlineRenameMetrics(bounds)); + return renameFrame.result.sessionStarted; +} + +void UpdateUIEditorTreeViewInlineRenameSession( + UIEditorInlineRenameSessionState& renameState, + UIEditorInlineRenameSessionFrame& renameFrame, + const UIRect& bounds, + const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents) { + UIEditorInlineRenameSessionRequest request = {}; + request.itemId = renameState.itemId; + request.initialText = renameState.textFieldSpec.value; + request.bounds = bounds; + renameFrame = UpdateUIEditorInlineRenameSession( + renameState, + request, + inputEvents, + BuildUIEditorTreeViewInlineRenameMetrics(bounds)); +} + +void AppendUIEditorTreeViewDropPreview( + ::XCEngine::UI::UIDrawList& drawList, + const Widgets::UIEditorTreeViewLayout& layout, + const std::vector& items, + bool active, + bool dropToRoot, + std::string_view dropTargetItemId, + const ::XCEngine::UI::UIColor& previewColor, + float borderThickness, + float cornerRounding) { + if (!active) { + return; + } + + if (dropToRoot) { + drawList.AddRectOutline( + layout.bounds, + previewColor, + borderThickness, + cornerRounding); + return; + } + + const std::size_t visibleIndex = + FindUIEditorTreeViewVisibleItemIndex(layout, items, dropTargetItemId); + if (visibleIndex == Widgets::UIEditorTreeViewInvalidIndex || + visibleIndex >= layout.rowRects.size()) { + return; + } + + drawList.AddRectOutline( + layout.rowRects[visibleIndex], + previewColor, + borderThickness, + cornerRounding); +} + +} // namespace XCEngine::UI::Editor diff --git a/new_editor/src/Collections/UIEditorTreeViewInteraction.cpp b/new_editor/src/Collections/UIEditorTreeViewInteraction.cpp index 3a6697c0..bc917bb5 100644 --- a/new_editor/src/Collections/UIEditorTreeViewInteraction.cpp +++ b/new_editor/src/Collections/UIEditorTreeViewInteraction.cpp @@ -1,4 +1,5 @@ #include +#include #include @@ -514,6 +515,30 @@ void EnsureVisibleSelection( } // namespace +std::vector<::XCEngine::UI::UIInputEvent> BuildUIEditorHostedTreeViewInputEvents( + const ::XCEngine::UI::UIRect& bounds, + const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents, + const UIEditorHostedTreeViewInputOptions& options, + bool synthesizeFocusGained, + bool synthesizeFocusLost) { + return BuildUIEditorPanelInputEvents( + bounds, + inputEvents, + UIEditorPanelInputFilterOptions{ + .allowPointerInBounds = options.allowInteraction, + .allowPointerWhileCaptured = options.captureActive, + .allowKeyboardInput = options.hasInputFocus, + .allowFocusEvents = + options.hasInputFocus || + options.captureActive || + synthesizeFocusGained || + synthesizeFocusLost, + .includePointerLeave = options.allowInteraction || options.captureActive + }, + synthesizeFocusGained, + synthesizeFocusLost); +} + UIEditorTreeViewInteractionFrame UpdateUIEditorTreeViewInteraction( UIEditorTreeViewInteractionState& state, UISelectionModel& selectionModel,