diff --git a/docs/plan/NewEditor_EditorLayerAppBoundaryClosurePlan_2026-04-22.md b/docs/plan/NewEditor_EditorLayerAppBoundaryClosurePlan_2026-04-22.md deleted file mode 100644 index a06fe0dd..00000000 --- a/docs/plan/NewEditor_EditorLayerAppBoundaryClosurePlan_2026-04-22.md +++ /dev/null @@ -1,561 +0,0 @@ -# NewEditor Editor Layer / App Boundary Closure Plan - -Date: 2026-04-22 - -## 1. Objective - -这份计划的目标不是泛泛讨论“分层好不好看”,而是把 `new_editor` 当前最明显的边界泄漏收口掉: - -1. `engine` 中的 UI 基础层 / 运行时层继续只负责通用 UI 能力 -2. `new_editor/include` + `new_editor/src` 中的 `XCUIEditorLib` 明确成为“编辑器 UI 模块层” -3. `new_editor/app` 只保留编辑器应用层应当持有的业务、平台、渲染宿主和应用装配 -4. 把当前明显散落在 `app`、但本质属于“通用 editor UI 机制”的内容,抽回 `XCUIEditorLib` - -本轮计划只聚焦已经确认影响较大的三类问题: - -1. hosted panel 运行时机制散落在 `app` -2. detached/tool-window 策略散落在多个 `app` 模块 -3. panel lifecycle / focus / input gate 已经在 editor 层有雏形,但 `app` 没有接上,反而重复实现 - -## 2. Confirmed Findings - -### 2.1 Dependency direction is mostly correct - -当前 `XCUIEditorLib` 没有实际反向 include `app`,说明“目录依赖方向”本身没有彻底坏掉。 - -这点必须保留: - -1. `engine` 不依赖 `new_editor` -2. `XCUIEditorLib` 不依赖 `new_editor/app` -3. `app` 依赖 `XCUIEditorLib` 和 host/platform/rendering ports - -所以本次工作不是纠正反向依赖,而是纠正“职责泄漏到 `app`”。 - -### 2.2 Hosted panel runtime is still implemented in app - -以下职责目前散落在 `app`: - -1. 具体 panel 实例持有与总调度: - - `new_editor/app/Composition/EditorShellRuntime.h` -2. 面板更新分发、输入过滤、焦点派发: - - `new_editor/app/Composition/EditorShellHostedPanelCoordinator.cpp` -3. 面板绘制拼装: - - `new_editor/app/Composition/EditorShellDrawComposer.cpp` -4. 面板事件回流到 session/status: - - `new_editor/app/Composition/WorkspaceEventSync.cpp` - -问题不在于这些代码位于 `app`,而在于它们实现的内容已经不是“应用业务”,而是“通用 editor-hosted-panel 运行时机制”。 - -### 2.3 Detached window policy is duplicated in multiple app modules - -当前至少有三处各自推导 detached/tool-window 规则: - -1. shell 交互期的 metrics 调整: - - `new_editor/app/Composition/EditorShellInteractionEngine.cpp` -2. 窗口 chrome 的 tool-window 判定和最小尺寸: - - `new_editor/app/Platform/Win32/EditorWindowChromeController.cpp` -3. 打开 detached panel 时的 preferred size 解析: - - `new_editor/app/Platform/Win32/EditorWindowFrameOrchestrator.cpp` - -但这些规则依赖的源数据其实都在: - -1. `UIEditorPanelRegistry` -2. `UIEditorWorkspaceController` -3. `UIEditorWindowWorkspaceController` - -也就是说,策略消费被拆散了,策略数据却本来就在 editor 层。 - -### 2.4 Panel lifecycle abstraction exists in editor layer but is not used - -`XCUIEditorLib` 已经有: - -1. `new_editor/include/XCEditor/Panels/UIEditorPanelHostLifecycle.h` -2. `new_editor/src/Panels/UIEditorPanelHostLifecycle.cpp` - -但 `app` 里仍然自己维护: - -1. `PanelInputContext` -2. `allowInteraction` -3. `focusGained` -4. `focusLost` -5. mounted panel 扫描 - -对应文件包括: - -1. `new_editor/app/Composition/EditorShellHostedPanelCoordinator.cpp` -2. `new_editor/app/Features/PanelInputContext.h` -3. `new_editor/app/Features/Hierarchy/HierarchyPanel.cpp` -4. `new_editor/app/Features/Project/ProjectPanel.cpp` -5. `new_editor/app/Features/Inspector/InspectorPanel.cpp` -6. `new_editor/app/Features/ColorPicker/ColorPickerPanel.cpp` -7. `new_editor/app/Features/Console/ConsolePanel.cpp` - -这会导致同一套 host lifecycle 语义出现两份实现,并最终行为漂移。 - -### 2.5 Resulting symptom: EditorShellRuntime is carrying too much generic mechanism - -`EditorShellRuntime` 现在同时承担: - -1. shell interaction orchestration -2. hosted panel runtime orchestration -3. viewport host service orchestration -4. app feature update routing -5. draw packet assembly -6. workspace event tracing and status backflow - -其中前半部分有相当一块本应属于 editor 模块层,而不是 app 层。 - -## 3. Boundary Rules - -## 3.1 What must stay in engine UI base/runtime layers - -`engine` 中的 UI 层继续只负责: - -1. `UIInputEvent`、`UIRect`、`UIDrawData` 等基础类型 -2. 通用 UI 输入、焦点、样式、文字、widget model -3. 与编辑器无关的 runtime UI 能力 - -`engine` 不负责: - -1. editor panel host lifecycle -2. docked editor shell 策略 -3. detached editor window 策略 -4. editor hosted panel 调度 - -## 3.2 What must belong to XCUIEditorLib - -`XCUIEditorLib` 应当负责所有“与具体业务面板无关、但与 editor shell / workspace / panel host 强相关”的通用机制,包括: - -1. panel registry 元数据及校验 -2. workspace / dock host / viewport shell 的通用交互 -3. hosted panel mount/layout/lifecycle 的统一推导 -4. hosted panel 输入过滤与 input-owner 对接 -5. detached/tool-window 策略与尺寸解析 -6. 供应用层消费的 panel dispatch frame / request / result - -这里的核心判断标准是: - -只要某段逻辑不关心“这是 Hierarchy 还是 Project”,而只关心“这是一个 externally hosted panel”,它就应该在 `XCUIEditorLib`。 - -## 3.3 What must stay in app - -`new_editor/app` 只保留以下内容: - -1. 具体业务 panel 本身: - - `HierarchyPanel` - - `ProjectPanel` - - `InspectorPanel` - - `ConsolePanel` - - `ColorPickerPanel` - - `SceneViewportFeature` -2. 业务 runtime: - - `EditorSceneRuntime` - - `EditorProjectRuntime` - - `EditorSession` -3. 应用装配: - - 菜单/命令/toolbar 的应用级定义 - - panel registry 的应用级实例构建 -4. 平台宿主: - - Win32 window lifecycle - - D3D12 host renderer - - capture/system interaction - -## 3.4 What must not be moved into XCUIEditorLib - -本次收口不是把所有东西都塞进 editor 库。 - -以下内容明确不下沉: - -1. Hierarchy / Project / Inspector 的业务数据模型 -2. Scene runtime / project runtime / selection service -3. Win32 chrome 和窗口消息循环 -4. D3D12 host 层资源与 swapchain -5. app 级菜单、命令、panel 组合方案 - -## 3.5 Placement rule for new modules - -当前 `new_editor/src/App` 实际没有形成可用公共层,`include/XCEditor/App` 也不存在。 - -因此新增 editor-layer 模块不应新建一个伪 `XCEditor/App`。 - -新增通用能力应优先落到: - -1. `XCEditor/Panels` -2. `XCEditor/Shell` -3. `XCEditor/Workspace` - -只在确实与上述三个域都不匹配时,才考虑新增域。 - -## 4. Target End State - -## 4.1 Target runtime flow - -目标运行流应当变成: - -1. `app` 构造应用级 `EditorShellAsset`、业务 panel、业务 runtime -2. `XCUIEditorLib` 负责: - - shell/workspace 交互 - - content host mount frame - - panel host lifecycle frame - - hosted panel dispatch frame - - detached window policy resolution -3. `app` 只做两件事: - - 把 dispatch frame 按 `panelId` 路由给具体 panel - - 把具体 panel 的业务事件回流到 app session/runtime - -这样 `app` 保留“内容”,`XCUIEditorLib` 收回“机制”。 - -## 4.2 Target module responsibilities - -### Editor layer should own - -建议新增或重组出以下 editor-layer 模块: - -1. `UIEditorHostedPanelDispatch` - - 统一生成每个 hosted panel 的 update request / input slice / lifecycle view -2. `UIEditorDetachedWindowPolicy` - - 统一解析 tool-window、preferred size、minimum size、detached title behavior -3. `UIEditorPanelHostLifecycle` - - 从“已有但未接入”变为“活跃主路径能力” - -### App layer should own - -`app` 最终只保留一个轻量路由层,例如: - -1. `EditorHostedPanelRouter` - - 根据 `panelId` 找到具体 panel 对象 - - 调用 `Update(...)` - - 调用 `Append(...)` - - 收集业务事件 - -如果不需要单独新建这个类,也至少要把现有 `EditorShellHostedPanelCoordinator` 收缩到这个角色。 - -## 4.3 Target data model direction - -本次收口后,`app` 不应再自己定义一套 panel host 语义。 - -目标是: - -1. 删除或废弃 `PanelInputContext` -2. 用 editor-layer 的统一 dispatch entry / lifecycle frame 取代 -3. hosted panel 的 mounted / visible / active / focused / focus gained / focus lost 只存在一套来源 - -## 5. Refactor Strategy - -### Phase A. Freeze boundary and add structural guardrails - -目标: - -在开始搬迁逻辑前,先把边界约束写清楚,避免中途再次把通用 editor 机制塞回 `app`。 - -要做的事: - -1. 在文档中明确本计划的边界规则 -2. 审查 `new_editor/CMakeLists.txt` 中 `XCUIEditorLib` 与 `app` 的 include 使用现状 -3. 增加最小结构验证规则: - - `new_editor/src` / `new_editor/include` 不反向 include `app` - - `app` 不再新增 editor-generic duplicated helper -4. 为后续 grep 验证准备检查项 - -验收标准: - -1. 本计划作为边界基线落地 -2. 后续每一阶段都能用 grep 验证“重复逻辑是否还活着” - -### Phase B. Make panel host lifecycle a live path - -目标: - -先把已有的 `UIEditorPanelHostLifecycle` 从死代码变成活代码。 - -要做的事: - -1. 在 editor-layer 主路径中接入 `UpdateUIEditorPanelHostLifecycle(...)` -2. 明确 `focusedPanelId` 的统一来源 -3. 让 lifecycle frame 成为 hosted panel dispatch 的基础输入 -4. 禁止 `app` 再自行推导: - - `focusGained` - - `focusLost` - - `active` - - `visible` - - `attached` - -重点文件范围: - -1. `new_editor/include/XCEditor/Panels/UIEditorPanelHostLifecycle.h` -2. `new_editor/src/Panels/UIEditorPanelHostLifecycle.cpp` -3. `new_editor/include/XCEditor/Shell/UIEditorShellInteraction.h` -4. `new_editor/include/XCEditor/Workspace/UIEditorWorkspaceInteraction.h` -5. `new_editor/app/Composition/EditorShellHostedPanelCoordinator.cpp` - -验收标准: - -1. `UpdateUIEditorPanelHostLifecycle(...)` 进入实际 frame path -2. `app` 不再自己计算 focus enter/leave 语义 - -### Phase C. Introduce hosted panel dispatch into XCUIEditorLib - -目标: - -把 hosted panel 的通用运行时机制从 `app` 抽回 editor 层,但不把具体业务 panel 也硬搬进库里。 - -建议新增能力: - -1. `UIEditorHostedPanelDispatchEntry` -2. `UIEditorHostedPanelDispatchFrame` -3. `UIEditorHostedPanelDispatchRequest` - -每个 entry 至少应包含: - -1. `panelId` -2. mounted/bounds 信息 -3. lifecycle state 或 lifecycle frame 视图 -4. 该 panel 可见时应收到的过滤后输入 -5. 交互是否允许 -6. 当前 input owner / capture owner 相关视图 - -要做的事: - -1. 把 `EditorShellHostedPanelCoordinator.cpp` 中的通用逻辑下沉到 `XCUIEditorLib` -2. 统一处理 hosted-content 与 shell 对 pointer stream 的让渡规则 -3. 统一处理 panel 的 mounted/bounds 查找 -4. 统一处理 focus/lifecycle/input gate 计算 -5. app 只保留: - - `panelId -> concrete panel` 路由 - - 业务对象依赖注入 - - 业务事件回流 - -重点文件范围: - -1. 新增: - - `new_editor/include/XCEditor/Panels/UIEditorHostedPanelDispatch.h` - - `new_editor/src/Panels/UIEditorHostedPanelDispatch.cpp` -2. 调整: - - `new_editor/app/Composition/EditorShellHostedPanelCoordinator.h` - - `new_editor/app/Composition/EditorShellHostedPanelCoordinator.cpp` - - `new_editor/app/Features/PanelInputContext.h` - - `new_editor/app/Features/Hierarchy/HierarchyPanel.*` - - `new_editor/app/Features/Project/ProjectPanel.*` - - `new_editor/app/Features/Inspector/InspectorPanel.*` - - `new_editor/app/Features/ColorPicker/ColorPickerPanel.*` - - `new_editor/app/Features/Console/ConsolePanel.*` - -验收标准: - -1. `PanelInputContext` 被删除或降级为纯 app-specific 数据,不再承载通用 host 语义 -2. `FindMountedHierarchyPanel`、`FindMountedProjectPanel` 等重复 mounted 扫描逻辑被消除或大幅收缩 -3. `EditorShellHostedPanelCoordinator` 只剩业务路由和依赖注入 - -### Phase D. Centralize detached window / tool-window policy - -目标: - -把 detached/tool-window 相关策略从多个 `app` 模块收成一套 editor-layer 能力。 - -建议新增能力: - -1. `UIEditorDetachedWindowPolicy` -2. `UIEditorDetachedWindowPolicyResult` -3. `ResolveUIEditorDetachedWindowPolicy(...)` - -这套策略至少统一回答: - -1. 当前 workspace 是否表现为 tool-window -2. 当前 detached window 的 preferred/minimum 尺寸是多少 -3. 是否应使用 detached title-bar tab strip -4. 单面板 root tab stack 的判定逻辑 - -要做的事: - -1. 把 `ResolveSingleVisibleToolWindowPanelDescriptor(...)` 和同类逻辑下沉 -2. 把 `ResolveMinimumOuterSize(...)`、`ResolveDetachedPanelPreferredSize(...)` 统一消费同一套 API -3. 让 shell interaction metrics、Win32 chrome、detached-panel open request 共用同一套策略结果 - -重点文件范围: - -1. 新增: - - `new_editor/include/XCEditor/Shell/UIEditorDetachedWindowPolicy.h` - - `new_editor/src/Shell/UIEditorDetachedWindowPolicy.cpp` -2. 调整: - - `new_editor/app/Composition/EditorShellInteractionEngine.cpp` - - `new_editor/app/Platform/Win32/EditorWindowChromeController.cpp` - - `new_editor/app/Platform/Win32/EditorWindowFrameOrchestrator.cpp` - -验收标准: - -1. `toolWindow`、preferred size、minimum size 的判定只有一套主实现 -2. 上述三个 app 文件只消费统一 helper,不再保留平行推导函数 - -### Phase E. Reduce EditorShellRuntime to app orchestration - -目标: - -在完成通用机制下沉之后,让 `EditorShellRuntime` 从“半个 editor framework”收缩为“应用层编排器”。 - -要做的事: - -1. 移除 `EditorShellRuntime` 中与 hosted panel lifecycle / dispatch / policy 直接相关的通用逻辑 -2. 保留: - - app feature 依赖注入 - - scene/project runtime 同步 - - status / selection / command route 回流 - - viewport host 服务调用 -3. 视情况把 `EditorShellDrawComposer` 收缩为纯 draw packet 组织器 -4. 视情况把 `WorkspaceEventSync` 收缩为纯 app 业务事件同步器 - -重点文件范围: - -1. `new_editor/app/Composition/EditorShellRuntime.h` -2. `new_editor/app/Composition/EditorShellRuntime.cpp` -3. `new_editor/app/Composition/EditorShellDrawComposer.h` -4. `new_editor/app/Composition/EditorShellDrawComposer.cpp` -5. `new_editor/app/Composition/WorkspaceEventSync.h` -6. `new_editor/app/Composition/WorkspaceEventSync.cpp` - -验收标准: - -1. `EditorShellRuntime` 不再承载 editor-generic host policy -2. 它只负责应用装配和业务流程编排 - -### Phase F. Verification and regression hardening - -目标: - -确保这次收口不是“结构更好看了,但行为退化了”。 - -要做的事: - -1. 编译 `XCUIEditorLib`、`XCUIEditorAppCore`、`XCUIEditorApp` -2. 验证 primary window、detached window、tool-window 三种路径 -3. 验证以下交互没有回归: - - hierarchy 选择/拖拽/rename - - project tree/grid 交互 - - inspector 输入 - - color picker detached open - - scene viewport capture -4. 增加 grep 结构检查: - - `PanelInputContext` 不再作为通用 host 语义入口 - - `ResolveSingleVisibleToolWindowPanelDescriptor` 等重复 helper 已移除 - - `UpdateUIEditorPanelHostLifecycle` 已有活跃调用 - -## 6. File-Level Scope - -### 6.1 Existing editor-layer files to extend - -1. `new_editor/include/XCEditor/Panels/UIEditorPanelHostLifecycle.h` -2. `new_editor/src/Panels/UIEditorPanelHostLifecycle.cpp` -3. `new_editor/include/XCEditor/Panels/UIEditorPanelContentHost.h` -4. `new_editor/src/Panels/UIEditorPanelContentHost.cpp` -5. `new_editor/include/XCEditor/Workspace/UIEditorWorkspaceInteraction.h` -6. `new_editor/src/Workspace/UIEditorWorkspaceInteraction.cpp` -7. `new_editor/include/XCEditor/Shell/UIEditorShellInteraction.h` - -### 6.2 New editor-layer files expected - -1. `new_editor/include/XCEditor/Panels/UIEditorHostedPanelDispatch.h` -2. `new_editor/src/Panels/UIEditorHostedPanelDispatch.cpp` -3. `new_editor/include/XCEditor/Shell/UIEditorDetachedWindowPolicy.h` -4. `new_editor/src/Shell/UIEditorDetachedWindowPolicy.cpp` - -如果实际实现中发现这些文件更适合放到 `Workspace/`,可以调整,但不能回退到 `app`。 - -### 6.3 App files expected to shrink - -1. `new_editor/app/Composition/EditorShellHostedPanelCoordinator.h` -2. `new_editor/app/Composition/EditorShellHostedPanelCoordinator.cpp` -3. `new_editor/app/Composition/EditorShellInteractionEngine.cpp` -4. `new_editor/app/Composition/EditorShellRuntime.h` -5. `new_editor/app/Composition/EditorShellRuntime.cpp` -6. `new_editor/app/Platform/Win32/EditorWindowChromeController.cpp` -7. `new_editor/app/Platform/Win32/EditorWindowFrameOrchestrator.cpp` -8. `new_editor/app/Features/PanelInputContext.h` - -### 6.4 App business files expected to adapt, not be generalized - -1. `new_editor/app/Features/Hierarchy/HierarchyPanel.*` -2. `new_editor/app/Features/Project/ProjectPanel.*` -3. `new_editor/app/Features/Inspector/InspectorPanel.*` -4. `new_editor/app/Features/ColorPicker/ColorPickerPanel.*` -5. `new_editor/app/Features/Console/ConsolePanel.*` -6. `new_editor/app/Features/Scene/SceneViewportFeature.*` - -这些文件应改为消费 editor-layer 的统一 dispatch/lifecycle 数据,而不是继续维护自己的 host 规则。 - -## 7. Validation - -## 7.1 Structural validation - -完成后必须满足: - -1. `XCUIEditorLib` 仍然不 include `app` -2. `UpdateUIEditorPanelHostLifecycle(...)` 已进入活跃路径 -3. `app` 中不再保留三套 detached/tool-window 策略推导 -4. `app` 中不再保留多套 mounted/focus/allowInteraction 推导 - -## 7.2 Behavior validation - -至少验证以下行为: - -1. 主窗口正常更新/绘制 -2. detached panel 正常打开 -3. tool-window 最小尺寸和标题栏行为正确 -4. hierarchy / project / inspector / color picker 交互不回归 -5. scene viewport capture / pointer ownership 不回归 -6. 跨窗口面板移动后生命周期和交互状态仍然正确 - -## 7.3 Regression grep checklist - -收口完成后,以下 grep 结果应显著收缩或归零: - -1. `FindMountedHierarchyPanel` -2. `FindMountedProjectPanel` -3. `FindMountedInspectorPanel` -4. `FindMountedColorPickerPanel` -5. `PanelInputContext` -6. `ResolveSingleVisibleToolWindowPanelDescriptor` -7. `ResolveSingleVisibleRootPanelDescriptor` -8. `ResolveMinimumOuterSize` -9. `ResolveDetachedPanelPreferredSize` - -## 8. Execution Order - -建议按以下顺序执行,降低回归面: - -1. 先接通 `UIEditorPanelHostLifecycle` 活路径 -2. 再引入 `UIEditorHostedPanelDispatch` -3. 然后让各个 panel 改为消费统一 dispatch entry -4. 再统一 detached/tool-window policy -5. 最后收缩 `EditorShellRuntime`、`EditorShellHostedPanelCoordinator` 和 `EditorShellDrawComposer` - -不要反过来先大拆 `EditorShellRuntime`,否则会在旧重复逻辑还没收掉时放大回归风险。 - -## 9. Out Of Scope - -本计划明确不包含: - -1. 把业务 panel 本身迁入 `XCUIEditorLib` -2. 重做 Win32 window manager -3. 重做 D3D12 host renderer -4. 重做 viewport render pipeline -5. 全面重命名 `new_editor` 目录结构 - -## 10. Completion Criteria - -只有满足以下条件,这次分层收口才算真正完成: - -1. `app` 中不再保存 editor-generic hosted-panel 机制实现 -2. detached/tool-window 策略收成 editor-layer 单一入口 -3. `UIEditorPanelHostLifecycle` 从闲置工具变为真实主路径能力 -4. 具体业务 panel 继续留在 `app`,但只消费统一的 editor-layer dispatch/lifecycle 数据 -5. `EditorShellRuntime` 收缩为应用编排器,而不是第二套 editor framework -6. `XCUIEditorLib` 与 `app` 的单向依赖方向保持不变 - -## 11. Final Statement - -这次收口的目标不是“把更多代码搬到 `new_editor/src` 就算成功”,而是: - -1. 让 `XCUIEditorLib` 真的承担 editor 模块层职责 -2. 让 `app` 退回应用层职责 -3. 让同一套 panel host / detached-window 语义只保留一份实现 - -做到这三点之后,`new_editor` 的四层结构才算从“目录上分层”走到“职责上分层”。 diff --git a/docs/plan/NewEditor_TreeActualRedundancyReductionPlan_2026-04-22.md b/docs/plan/NewEditor_TreeActualRedundancyReductionPlan_2026-04-22.md new file mode 100644 index 00000000..0fe83de8 --- /dev/null +++ b/docs/plan/NewEditor_TreeActualRedundancyReductionPlan_2026-04-22.md @@ -0,0 +1,187 @@ +# NewEditor Tree Actual Redundancy Reduction Plan + +Date: `2026-04-22` +Status: `In Progress` + +## Goal + +- Remove only the redundancy that is structurally real in `new_editor` tree-related code. +- Keep the existing panel refresh split, frame ownership, and hosted-panel dispatch architecture intact. +- Accept only changes that produce clear net code reduction and keep ownership clearer, not blurrier. + +## Why The Previous Plan Was Deleted + +- The deleted plan incorrectly treated the rename flow as a shared tree host skeleton. +- `HierarchyPanel` is a single-tree, single-surface rename owner. +- `ProjectPanel` is a tree/grid dual-surface rename owner and its rename bounds, state, and commit side-effects differ materially. +- Because of that difference, extracting rename state handling upward adds request/status/surface glue instead of deleting real code. +- Any cross-panel rename abstraction is now considered a wrong direction for this codebase. + +## Hard Constraints + +- `HierarchyPanel` / `ProjectPanel` rename flow is explicitly not a dedup target. +- Do not merge panel refresh loops. +- Do not merge project tree and asset grid behavior. +- Do not change viewport independent request/render flow. +- Do not change `EditorWindowFrameDriver` single frame owner semantics. +- Do not move project or scene runtime semantics into `XCEditor`. +- Do not create new `Behavior`, `Host`, `Helper`, `Support`, `Tooling`, or similar glue files. +- Any phase must have a credible negative net diff before implementation starts. + +## Real Remaining Redundancy Targets + +### 1. `ProjectPanel` Left-Tree Internal Repetition + +This is the first valid target. + +Current repetition shape: + +- repeated left-tree layout rebuild sequences after navigation / drop / refresh paths +- repeated left-tree draw call setup for background / foreground / rename overlay / drop preview +- repeated left-tree state sync and cleanup around operations inside the same panel + +Rules: + +- Keep this work inside `ProjectPanel` and existing tree modules. +- Prefer local private functions or same-file narrowing over new files. +- Do not generalize tree/grid business logic together. + +### 2. Cross-Panel Tree Draw Chain Re-Audit + +This is only a candidate target, not an automatic extraction target. + +Possible shared shape: + +- append tree background +- append tree foreground +- append inline rename overlay +- append drop preview + +Rules: + +- Only extract if the result is a pure UI draw chain with no business ownership leakage. +- Only extract into existing `UIEditorTreeView.*` if total code clearly goes down. +- If the net diff is not negative, stop and keep the code local. + +### 3. Final Intentional-Duplication Audit + +After the valid reductions above, re-check what remains. + +Expected intentional duplicates: + +- rename ownership between `HierarchyPanel` and `ProjectPanel` +- scene-specific drag/drop commit behavior +- project-specific folder / asset / breadcrumb / splitter / context menu orchestration + +If a remaining overlap is only superficial orchestration around different business surfaces, record it as intentional and stop. + +## Execution Plan + +### Phase A. Freeze The Rename Boundary + +Status: `Completed` + +Target: + +- Remove the invalid rename-dedup direction from the active plan baseline. + +Completed result: + +- Deleted the wrong plan that targeted cross-panel rename host dedup. +- Re-established rename as panel-owned business logic, not a shared tree abstraction target. + +Validation: + +- Documentation baseline corrected. + +### Phase B. Reduce `ProjectPanel` Left-Tree Internal Repetition + +Status: `Completed` + +Target: + +- Shrink repeated left-tree orchestration inside `ProjectPanel` without changing behavior boundaries. + +Allowed scope: + +- `new_editor/app/Features/Project/ProjectPanel.cpp` +- `new_editor/app/Features/Project/ProjectPanel.h` + +Rules: + +- No new file. +- No tree/grid semantic merge. +- No rename abstraction promotion. +- Only keep helpers that delete more code than they add. + +Validation: + +- `cmake --build build --config Debug --target XCUIEditorApp` +- smoke-launch `build/new_editor/Debug/XCUIEditor.exe` +- confirm latest `runtime.log` contains: + - `EnsureEditorStartupScene loaded scene=Main Scene` + - `[app] initialize end` + - `[app] shutdown end` + +Completed result: + +- Reduced the real repeated reconcile chain inside `ProjectPanel`. +- Kept tree/grid business ownership split intact. +- Landed local private narrowing only: + - runtime-to-UI selection sync now funnels through one panel-local path + - layout/tree-frame rebuild now funnels through one panel-local path +- `ProjectBrowserModel` also now owns a single browser-state refresh chain instead of repeating: + - `RefreshFolderTree()` + - `EnsureValidCurrentFolder()` + - `RefreshAssetList()` + +### Phase C. Re-Audit The Shared Tree Draw Chain + +Status: `Pending` + +Target: + +- Check whether a narrow draw-chain entry in existing `UIEditorTreeView.*` can reduce real code across `HierarchyPanel` and the Project left tree. + +Allowed scope: + +- `new_editor/include/XCEditor/Collections/UIEditorTreeView.h` +- `new_editor/src/Collections/UIEditorTreeView.cpp` +- the two panel files only where call sites get smaller + +Rules: + +- No state machine extraction. +- No rename surface abstraction. +- No drag/drop business extraction. +- Cancel the phase immediately if the predicted diff is not net-negative. + +Validation: + +- `rg` verification of targeted duplicate draw blocks +- `cmake --build build --config Debug --target XCUIEditorApp` +- smoke-launch `build/new_editor/Debug/XCUIEditor.exe` + +### Phase D. Final Closure Audit + +Status: `Pending` + +Target: + +- Verify that all remaining overlap is either reduced or explicitly intentional. + +Validation: + +- targeted `rg` audit +- `git diff --stat` confirms accepted phases reduced code overall +- `cmake --build build --config Debug --target XCUIEditorApp` +- smoke-launch `build/new_editor/Debug/XCUIEditor.exe` + +## Acceptance + +- The wrong rename-dedup plan no longer exists in `docs/plan`. +- Cross-panel rename dedup is explicitly excluded. +- The next implementation target is `ProjectPanel` left-tree internal repetition, not rename. +- No new glue-layer file is introduced. +- Each accepted phase must reduce code, not grow it. +- Build and smoke validation pass after each completed implementation phase. diff --git a/docs/plan/NewEditor_XCUIEditorLibRetirementPlan_2026-04-22.md b/docs/plan/NewEditor_XCUIEditorLibRetirementPlan_2026-04-22.md new file mode 100644 index 00000000..b6e2e7d5 --- /dev/null +++ b/docs/plan/NewEditor_XCUIEditorLibRetirementPlan_2026-04-22.md @@ -0,0 +1,90 @@ +# NewEditor XCUIEditorLib Retirement Plan + +Date: `2026-04-22` +Status: `Completed` + +## Goal + +- Remove `XCUIEditorLib` as a first-class build target from `new_editor`. +- Stop treating `new_editor/include + new_editor/src` as a separately promoted module-layer library in the current product build. +- Flatten the current XCUI shared sources directly back into `XCUIEditorApp`, so the build graph matches the actual product shape. + +## Why The Previous Plan Was Deleted + +- The deleted plan assumed `XCUIEditorLib` should keep growing into a formal editor module layer. +- That premise conflicts with the current architecture direction and with the explicit requirement that this extra lib layer should not continue to exist. +- Continuing to move logic into `XCUIEditorLib` would deepen the wrong boundary instead of removing it. + +## Current Reality + +- `XCUIEditorLib` is only defined in `new_editor/CMakeLists.txt`. +- `XCUIEditorApp` is the only active build target that links it. +- The current source lists are already centralized in one file, so removing the lib layer is mainly a build-graph flattening task, not a source-tree migration task. + +## Constraints + +- Do not introduce a renamed replacement library target. +- Do not replace `XCUIEditorLib` with another fake public layer. +- Do not move app business sources into `new_editor/src`. +- Do not split this into new glue targets just to preserve the old layering narrative. +- Keep the active executable target as `XCUIEditorApp`. + +## Execution Plan + +### Phase A. Remove The Wrong Plan Baseline + +Status: `Completed` + +Target: + +- Delete the invalid `XCUIEditorLib`-promotion plan from `docs/plan`. +- Replace it with a retirement plan that matches the actual desired direction. + +Validation: + +- `docs/plan` no longer contains the deleted plan. +- The active plan explicitly treats `XCUIEditorLib` as a retirement target, not a destination layer. + +### Phase B. Flatten XCUIEditorLib Into XCUIEditorApp + +Status: `Completed` + +Target: + +- Remove the `add_library(XCUIEditorLib ...)` target. +- Compile the same `include/src` source set directly as part of `XCUIEditorApp`. +- Move any target-local include paths or compile settings required by those sources onto `XCUIEditorApp`. +- Remove the executable link dependency on `XCUIEditorLib`. +- Clean up stale post-build visible lib artifacts if needed. + +Validation: + +- `new_editor/CMakeLists.txt` no longer defines `XCUIEditorLib`. +- `XCUIEditorApp` directly owns the old XCUI shared source set. +- `XCUIEditorApp` no longer links `XCUIEditorLib`. + +### Phase C. Reconfigure, Build, Smoke + +Status: `Completed` + +Target: + +- Reconfigure `build/new_editor`. +- Build `XCUIEditorApp`. +- Launch `build/new_editor/Debug/XCUIEditor.exe` for a short smoke run. + +Validation: + +- Configure succeeds. +- Build reaches `XCUIEditorApp`. +- Smoke log contains: + - `EnsureEditorStartupScene loaded scene=Main Scene` + - `[app] initialize end` + - `[app] shutdown end` + +## Acceptance + +- `XCUIEditorLib` is no longer an active build target in `new_editor/CMakeLists.txt`. +- `XCUIEditorApp` becomes the direct owner of the former XCUI shared source set. +- No replacement fake public lib target is introduced. +- The active plan no longer frames `XCUIEditorLib` as a future architecture destination. diff --git a/new_editor/CMakeLists.txt b/new_editor/CMakeLists.txt index 2c9f6b54..77a3c7f3 100644 --- a/new_editor/CMakeLists.txt +++ b/new_editor/CMakeLists.txt @@ -135,7 +135,7 @@ set(XCUI_EDITOR_WIDGET_SUPPORT_SOURCES src/Widgets/UIEditorFieldRowLayout.cpp ) -add_library(XCUIEditorLib STATIC +set(XCUI_EDITOR_SHARED_SOURCES ${XCUI_EDITOR_FOUNDATION_SOURCES} ${XCUI_EDITOR_FIELD_SOURCES} ${XCUI_EDITOR_COLLECTION_SOURCES} @@ -148,25 +148,6 @@ add_library(XCUIEditorLib STATIC ${XCUI_EDITOR_WIDGET_SUPPORT_SOURCES} ) -target_include_directories(XCUIEditorLib - PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR}/include - ${CMAKE_SOURCE_DIR}/engine/include - PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR}/app - ${CMAKE_CURRENT_SOURCE_DIR}/src -) - -target_compile_definitions(XCUIEditorLib PUBLIC - XCUIEDITOR_REPO_ROOT="${XCUIEDITOR_REPO_ROOT_PATH}" -) - -xcui_editor_apply_common_target_settings(XCUIEditorLib PUBLIC) - -target_link_libraries(XCUIEditorLib PRIVATE - XCEngine -) - set(XCUI_EDITOR_HOST_PLATFORM_SOURCES app/Platform/Win32/BorderlessWindowChrome.cpp app/Platform/Win32/BorderlessWindowFrame.cpp @@ -288,6 +269,7 @@ if(XCENGINE_BUILD_XCUI_EDITOR_APP) ) set(XCUI_EDITOR_APP_INTERNAL_SOURCES + ${XCUI_EDITOR_SHARED_SOURCES} ${XCUI_EDITOR_HOST_PLATFORM_SOURCES} ${XCUI_EDITOR_HOST_RENDERING_SOURCES} ${XCUI_EDITOR_APP_CORE_SOURCES} @@ -302,6 +284,7 @@ if(XCENGINE_BUILD_XCUI_EDITOR_APP) target_include_directories(XCUIEditorApp PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/app ${CMAKE_CURRENT_SOURCE_DIR}/include + ${CMAKE_CURRENT_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/engine/include ${CMAKE_SOURCE_DIR}/engine/third_party/stb ) @@ -313,7 +296,6 @@ if(XCENGINE_BUILD_XCUI_EDITOR_APP) xcui_editor_apply_common_target_settings(XCUIEditorApp PRIVATE) target_link_libraries(XCUIEditorApp PRIVATE - XCUIEditorLib XCEngine XCEngineRenderingEditorSupport d3d12.lib @@ -340,6 +322,7 @@ if(XCENGINE_BUILD_XCUI_EDITOR_APP) $/XCUIEditorAppCore.lib $/XCUIEditorAppLib.lib $/XCUIEditorHost.lib + $/XCUIEditorLib.lib ) if(WIN32 AND XCENGINE_ENABLE_PHYSX) diff --git a/new_editor/app/Features/Project/ProjectBrowserModel.cpp b/new_editor/app/Features/Project/ProjectBrowserModel.cpp index dde41953..f2fb0e84 100644 --- a/new_editor/app/Features/Project/ProjectBrowserModel.cpp +++ b/new_editor/app/Features/Project/ProjectBrowserModel.cpp @@ -582,9 +582,7 @@ void ProjectBrowserModel::Initialize(const std::filesystem::path& repoRoot) { void ProjectBrowserModel::Refresh() { TraceProjectBrowser("ProjectBrowserModel::Refresh begin"); - RefreshFolderTree(); - EnsureValidCurrentFolder(); - RefreshAssetList(); + RefreshBrowserState(); TraceProjectBrowser( "ProjectBrowserModel::Refresh end currentFolder=" + m_currentFolderId + @@ -594,6 +592,12 @@ void ProjectBrowserModel::Refresh() { std::to_string(m_assetEntries.size())); } +void ProjectBrowserModel::RefreshBrowserState() { + RefreshFolderTree(); + EnsureValidCurrentFolder(); + RefreshAssetList(); +} + bool ProjectBrowserModel::Empty() const { return m_treeItems.empty(); } @@ -701,9 +705,7 @@ bool ProjectBrowserModel::CreateFolder( return false; } - RefreshFolderTree(); - EnsureValidCurrentFolder(); - RefreshAssetList(); + RefreshBrowserState(); if (createdFolderId != nullptr) { *createdFolderId = BuildRelativeItemId(newFolderPath, m_assetsRootPath); } @@ -768,9 +770,7 @@ bool ProjectBrowserModel::CreateMaterial( return false; } - RefreshFolderTree(); - EnsureValidCurrentFolder(); - RefreshAssetList(); + RefreshBrowserState(); if (createdItemId != nullptr) { *createdItemId = BuildRelativeItemId(materialPath, m_assetsRootPath); } @@ -828,9 +828,7 @@ bool ProjectBrowserModel::RenameItem( itemId, destinationItemId); } - RefreshFolderTree(); - EnsureValidCurrentFolder(); - RefreshAssetList(); + RefreshBrowserState(); if (renamedItemId != nullptr) { *renamedItemId = destinationItemId; } @@ -864,9 +862,7 @@ bool ProjectBrowserModel::DeleteItem(std::string_view itemId) { std::filesystem::remove_all(sourcePath.value()); RemoveMetaSidecarIfPresent(sourcePath.value()); - RefreshFolderTree(); - EnsureValidCurrentFolder(); - RefreshAssetList(); + RefreshBrowserState(); return true; } catch (...) { return false; @@ -958,9 +954,7 @@ bool ProjectBrowserModel::MoveItemToFolder( itemId, destinationItemId); } - RefreshFolderTree(); - EnsureValidCurrentFolder(); - RefreshAssetList(); + RefreshBrowserState(); if (movedItemId != nullptr) { *movedItemId = destinationItemId; } @@ -1062,9 +1056,7 @@ bool ProjectBrowserModel::ReparentFolder( m_currentFolderId, sourceFolderId, destinationFolderId); - RefreshFolderTree(); - EnsureValidCurrentFolder(); - RefreshAssetList(); + RefreshBrowserState(); if (movedFolderId != nullptr) { *movedFolderId = destinationFolderId; @@ -1126,9 +1118,7 @@ bool ProjectBrowserModel::MoveFolderToRoot( m_currentFolderId, sourceFolderId, destinationFolderId); - RefreshFolderTree(); - EnsureValidCurrentFolder(); - RefreshAssetList(); + RefreshBrowserState(); if (movedFolderId != nullptr) { *movedFolderId = destinationFolderId; diff --git a/new_editor/app/Features/Project/ProjectBrowserModel.h b/new_editor/app/Features/Project/ProjectBrowserModel.h index 3206d608..83e690dd 100644 --- a/new_editor/app/Features/Project/ProjectBrowserModel.h +++ b/new_editor/app/Features/Project/ProjectBrowserModel.h @@ -106,6 +106,7 @@ public: std::vector BuildAncestorFolderIds(std::string_view itemId) const; private: + void RefreshBrowserState(); void RefreshFolderTree(); void RefreshAssetList(); void EnsureValidCurrentFolder(); diff --git a/new_editor/app/Features/Project/ProjectPanel.cpp b/new_editor/app/Features/Project/ProjectPanel.cpp index d87a1f08..0276f00e 100644 --- a/new_editor/app/Features/Project/ProjectPanel.cpp +++ b/new_editor/app/Features/Project/ProjectPanel.cpp @@ -324,8 +324,7 @@ const std::vector& ProjectPanel::GetWindowTreeIte void ProjectPanel::Initialize(const std::filesystem::path& repoRoot) { m_ownedProjectRuntime = std::make_unique(); m_ownedProjectRuntime->Initialize(repoRoot); - SyncCurrentFolderSelection(); - SyncAssetSelectionFromRuntime(); + SyncSelectionsFromRuntime(); } void ProjectPanel::SetProjectRuntime(EditorProjectRuntime* projectRuntime) { @@ -334,8 +333,7 @@ void ProjectPanel::SetProjectRuntime(EditorProjectRuntime* projectRuntime) { } m_projectRuntime = projectRuntime; - SyncCurrentFolderSelection(); - SyncAssetSelectionFromRuntime(); + SyncSelectionsFromRuntime(); } void ProjectPanel::SetCommandFocusService( @@ -386,7 +384,8 @@ ProjectPanel::CursorKind ProjectPanel::GetCursorKind() const { bool ProjectPanel::HasActivePointerCapture() const { return m_splitterDragging || GridDrag::HasActivePointerCapture(m_assetDragState) || - TreeDrag::HasActivePointerCapture(m_treeDragState); + TreeDrag::HasActivePointerCapture(m_treeDragState) || + HasActiveUIEditorTreeViewPointerCapture(m_treeInteractionState); } const std::vector& ProjectPanel::GetFrameEvents() const { @@ -647,6 +646,11 @@ void ProjectPanel::SyncCurrentFolderSelection() { m_folderSelection.SetSelection(currentFolderId); } +void ProjectPanel::SyncSelectionsFromRuntime() { + SyncCurrentFolderSelection(); + SyncAssetSelectionFromRuntime(); +} + void ProjectPanel::SyncAssetSelectionFromRuntime() { const EditorProjectRuntime* runtime = ResolveProjectRuntime(); if (runtime == nullptr || !runtime->HasSelection()) { @@ -662,13 +666,24 @@ void ProjectPanel::SyncAssetSelectionFromRuntime() { m_assetSelection.ClearSelection(); } +Widgets::UIEditorTreeViewMetrics ProjectPanel::RebuildPanelLayout(const UIRect& bounds) { + const Widgets::UIEditorTreeViewMetrics treeMetrics = ResolveUIEditorTreeViewMetrics(); + m_layout = BuildLayout(bounds); + m_treeFrame.layout = Widgets::BuildUIEditorTreeViewLayout( + m_layout.treeRect, + GetWindowTreeItems(), + m_folderExpansion, + treeMetrics, + m_treeInteractionState.verticalOffset); + return treeMetrics; +} + bool ProjectPanel::NavigateToFolder(std::string_view itemId, EventSource source) { if (!ResolveProjectRuntime()->NavigateToFolder(itemId)) { return false; } - SyncCurrentFolderSelection(); - SyncAssetSelectionFromRuntime(); + SyncSelectionsFromRuntime(); m_hoveredAssetItemId.clear(); m_lastPrimaryClickedAssetId.clear(); EmitEvent( @@ -687,9 +702,8 @@ bool ProjectPanel::OpenProjectItem(std::string_view itemId, EventSource source) if (asset->directory) { const bool navigated = ResolveProjectRuntime()->OpenItem(asset->itemId); if (navigated && HasValidBounds(m_layout.bounds)) { - SyncCurrentFolderSelection(); - SyncAssetSelectionFromRuntime(); - m_layout = BuildLayout(m_layout.bounds); + SyncSelectionsFromRuntime(); + RebuildPanelLayout(m_layout.bounds); m_hoveredAssetItemId.clear(); EmitEvent( EventKind::FolderNavigated, @@ -1147,12 +1161,11 @@ UIEditorHostCommandDispatchResult ProjectPanel::DispatchAssetCommand( const auto finalizeCreatedAsset = [this](std::string_view createdItemId) { ClearRenameState(); - SyncCurrentFolderSelection(); m_hoveredAssetItemId.clear(); m_lastPrimaryClickedAssetId = std::string(createdItemId); m_lastPrimaryClickTime = {}; ResolveProjectRuntime()->SetSelection(createdItemId); - SyncAssetSelectionFromRuntime(); + SyncSelectionsFromRuntime(); const AssetEntry* createdAsset = FindAssetEntry(createdItemId); if (createdAsset == nullptr) { @@ -1184,7 +1197,7 @@ UIEditorHostCommandDispatchResult ProjectPanel::DispatchAssetCommand( if (target.containerFolder->itemId != GetBrowserModel().GetCurrentFolderId()) { NavigateToFolder(target.containerFolder->itemId, EventSource::GridSecondary); if (HasValidBounds(m_layout.bounds)) { - m_layout = BuildLayout(m_layout.bounds); + RebuildPanelLayout(m_layout.bounds); } } @@ -1216,7 +1229,7 @@ UIEditorHostCommandDispatchResult ProjectPanel::DispatchAssetCommand( if (target.containerFolder->itemId != GetBrowserModel().GetCurrentFolderId()) { NavigateToFolder(target.containerFolder->itemId, EventSource::GridSecondary); if (HasValidBounds(m_layout.bounds)) { - m_layout = BuildLayout(m_layout.bounds); + RebuildPanelLayout(m_layout.bounds); } } @@ -1370,8 +1383,7 @@ UIEditorHostCommandDispatchResult ProjectPanel::DispatchEditCommand( } ClearRenameState(); - SyncCurrentFolderSelection(); - SyncAssetSelectionFromRuntime(); + SyncSelectionsFromRuntime(); m_hoveredAssetItemId.clear(); m_lastPrimaryClickedAssetId.clear(); m_lastPrimaryClickTime = {}; @@ -1439,8 +1451,7 @@ void ProjectPanel::Update( if (GetBrowserModel().GetTreeItems().empty()) { ResolveProjectRuntime()->Refresh(); - SyncCurrentFolderSelection(); - SyncAssetSelectionFromRuntime(); + SyncSelectionsFromRuntime(); } RebuildWindowTreeItems(); @@ -1472,17 +1483,11 @@ void ProjectPanel::Update( dispatchEntry.allowInteraction); m_navigationWidth = ClampNavigationWidth(m_navigationWidth, dispatchEntry.bounds.width); - m_layout = BuildLayout(dispatchEntry.bounds); + const Widgets::UIEditorTreeViewMetrics treeMetrics = + RebuildPanelLayout(dispatchEntry.bounds); if (m_contextMenu.open) { RebuildContextMenu(); } - const Widgets::UIEditorTreeViewMetrics treeMetrics = ResolveUIEditorTreeViewMetrics(); - m_treeFrame.layout = Widgets::BuildUIEditorTreeViewLayout( - m_layout.treeRect, - GetWindowTreeItems(), - m_folderExpansion, - treeMetrics, - m_treeInteractionState.verticalOffset); m_treeFrame.result = {}; if ((m_renameState.active || !m_pendingRenameItemId.empty()) && @@ -1516,7 +1521,7 @@ void ProjectPanel::Update( m_treeFrame.result.selectedItemId != GetBrowserModel().GetCurrentFolderId()) { CloseContextMenu(); NavigateToFolder(m_treeFrame.result.selectedItemId, EventSource::Tree); - m_layout = BuildLayout(dispatchEntry.bounds); + RebuildPanelLayout(dispatchEntry.bounds); } if (m_treeFrame.result.renameRequested && !m_treeFrame.result.renameItemId.empty()) { @@ -1595,20 +1600,13 @@ void ProjectPanel::Update( const bool hadAssetSelection = ResolveProjectRuntime()->HasSelection(); CloseContextMenu(); ResolveProjectRuntime()->ClearSelection(); - SyncAssetSelectionFromRuntime(); + SyncSelectionsFromRuntime(); m_hoveredAssetItemId.clear(); m_lastPrimaryClickedAssetId.clear(); if (hadAssetSelection && !ResolveProjectRuntime()->HasSelection()) { EmitSelectionClearedEvent(EventSource::Tree); } - SyncCurrentFolderSelection(); - m_layout = BuildLayout(dispatchEntry.bounds); - m_treeFrame.layout = Widgets::BuildUIEditorTreeViewLayout( - m_layout.treeRect, - GetWindowTreeItems(), - m_folderExpansion, - treeMetrics, - m_treeInteractionState.verticalOffset); + RebuildPanelLayout(dispatchEntry.bounds); } struct ProjectAssetDragCallbacks { @@ -1707,7 +1705,6 @@ void ProjectPanel::Update( m_hoveredAssetItemId.clear(); m_lastPrimaryClickedAssetId.clear(); m_lastPrimaryClickTime = {}; - SyncCurrentFolderSelection(); const std::string movedItemId = assetDragCallbacks.movedItemId.empty() ? assetDragResult.draggedItemId @@ -1715,23 +1712,17 @@ void ProjectPanel::Update( if (const AssetEntry* movedAsset = FindAssetEntry(movedItemId); movedAsset != nullptr) { ResolveProjectRuntime()->SetSelection(movedItemId); - SyncAssetSelectionFromRuntime(); + SyncSelectionsFromRuntime(); EmitEvent(EventKind::AssetSelected, EventSource::GridDrag, movedAsset); } else { ResolveProjectRuntime()->ClearSelection(); - SyncAssetSelectionFromRuntime(); + SyncSelectionsFromRuntime(); if (hadAssetSelection && !ResolveProjectRuntime()->HasSelection()) { EmitSelectionClearedEvent(EventSource::GridDrag); } } - m_layout = BuildLayout(dispatchEntry.bounds); - m_treeFrame.layout = Widgets::BuildUIEditorTreeViewLayout( - m_layout.treeRect, - GetWindowTreeItems(), - m_folderExpansion, - treeMetrics, - m_treeInteractionState.verticalOffset); + RebuildPanelLayout(dispatchEntry.bounds); } const bool suppressPanelPointerEvents = @@ -1900,7 +1891,7 @@ void ProjectPanel::Update( m_layout.breadcrumbItems[releasedBreadcrumbIndex]; if (item.clickable) { NavigateToFolder(item.targetFolderId, EventSource::Breadcrumb); - m_layout = BuildLayout(dispatchEntry.bounds); + RebuildPanelLayout(dispatchEntry.bounds); } } m_pressedBreadcrumbIndex = kInvalidLayoutIndex; diff --git a/new_editor/app/Features/Project/ProjectPanel.h b/new_editor/app/Features/Project/ProjectPanel.h index 3b1102fd..7fc553da 100644 --- a/new_editor/app/Features/Project/ProjectPanel.h +++ b/new_editor/app/Features/Project/ProjectPanel.h @@ -198,7 +198,10 @@ private: bool DispatchContextMenuItem(std::string_view itemId); void AppendContextMenu(::XCEngine::UI::UIDrawList& drawList) const; void ClearRenameState(); + void SyncSelectionsFromRuntime(); void SyncAssetSelectionFromRuntime(); + Widgets::UIEditorTreeViewMetrics RebuildPanelLayout( + const ::XCEngine::UI::UIRect& bounds); void QueueRenameSession( std::string_view itemId, RenameSurface surface);