2026-03-27 14:40:29 +08:00
|
|
|
|
# ProjectPanel
|
|
|
|
|
|
|
|
|
|
|
|
**命名空间**: `XCEngine::Editor`
|
|
|
|
|
|
|
|
|
|
|
|
**类型**: `class`
|
|
|
|
|
|
|
|
|
|
|
|
**源文件**: `editor/src/panels/ProjectPanel.h`
|
|
|
|
|
|
|
2026-04-04 01:02:57 +08:00
|
|
|
|
**描述**: 项目资源浏览面板,负责把 `IProjectManager` 当前目录模型渲染成“目录树 + breadcrumb + 搜索 + 资源网格 + 上下文菜单 + 拖放”的 Project Browser。
|
2026-03-27 14:40:29 +08:00
|
|
|
|
|
|
|
|
|
|
## 概述
|
|
|
|
|
|
|
2026-04-04 01:02:57 +08:00
|
|
|
|
`ProjectPanel` 当前是 Editor 里项目资源浏览的前端外壳,但它不是资产数据库本身。
|
2026-03-27 14:40:29 +08:00
|
|
|
|
|
2026-04-04 01:02:57 +08:00
|
|
|
|
它的职责边界比较明确:
|
2026-03-27 14:40:29 +08:00
|
|
|
|
|
2026-04-04 01:02:57 +08:00
|
|
|
|
- 项目目录模型与选择状态来自 [IProjectManager](../../Core/IProjectManager/IProjectManager.md)
|
|
|
|
|
|
- 资源打开、创建、删除、重命名、移动等语义落到 [ProjectCommands](../../Commands/ProjectCommands/ProjectCommands.md)
|
|
|
|
|
|
- 少量通用交互协议由 [ProjectActionRouter](../../Actions/ProjectActionRouter/ProjectActionRouter.md) 提供
|
|
|
|
|
|
- 面板自身主要负责布局、即时模式 UI 状态和延迟动作收口
|
2026-03-27 14:40:29 +08:00
|
|
|
|
|
2026-04-04 01:02:57 +08:00
|
|
|
|
## 生命周期与公开入口
|
2026-03-27 14:40:29 +08:00
|
|
|
|
|
2026-04-04 01:02:57 +08:00
|
|
|
|
- [Constructor](Constructor.md)
|
|
|
|
|
|
创建标题固定为 `"Project"` 的面板。
|
|
|
|
|
|
- [Initialize](Initialize.md)
|
|
|
|
|
|
把项目路径初始化工作委托给当前 `IProjectManager`。
|
|
|
|
|
|
- [Render](Render.md)
|
|
|
|
|
|
执行完整的项目浏览器绘制与交互驱动。
|
2026-03-27 14:40:29 +08:00
|
|
|
|
|
2026-04-04 01:02:57 +08:00
|
|
|
|
## 面板内部状态
|
2026-03-27 14:40:29 +08:00
|
|
|
|
|
2026-04-04 01:02:57 +08:00
|
|
|
|
`ProjectPanel` 当前长期持有的主要是 UI 状态,而不是项目数据本体:
|
2026-03-29 01:36:53 +08:00
|
|
|
|
|
2026-04-04 01:02:57 +08:00
|
|
|
|
| 状态 | 作用 |
|
|
|
|
|
|
|------|------|
|
|
|
|
|
|
| `m_searchBuffer` | 当前目录搜索关键字 |
|
|
|
|
|
|
| `m_navigationWidth` | 左侧目录树宽度 |
|
|
|
|
|
|
| `m_folderTreeState` | 目录树展开状态 |
|
|
|
|
|
|
| `m_renameState` | 行内重命名状态 |
|
|
|
|
|
|
| `m_assetDragDropState` | 本帧拖放源 / 目标状态 |
|
|
|
|
|
|
| `m_deferredContextAction` | 延迟到本帧末尾执行的上下文菜单动作 |
|
2026-03-29 01:36:53 +08:00
|
|
|
|
|
2026-04-04 01:02:57 +08:00
|
|
|
|
## 初始化与每帧主流程
|
|
|
|
|
|
|
|
|
|
|
|
### [Initialize(projectPath)](Initialize.md)
|
|
|
|
|
|
|
|
|
|
|
|
当前实现只有一行:
|
|
|
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
|
m_context->GetProjectManager().Initialize(projectPath);
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
这说明 `ProjectPanel` 不自己扫描目录,也不自己构建项目树。
|
|
|
|
|
|
|
|
|
|
|
|
### [Render()](Render.md)
|
|
|
|
|
|
|
|
|
|
|
|
当前每帧主要顺序是:
|
|
|
|
|
|
|
|
|
|
|
|
1. 打开 `PanelWindowScope`
|
|
|
|
|
|
2. 调用 `ObserveFocusedActionRoute(*m_context, EditorActionRoute::Project)`
|
|
|
|
|
|
3. `BeginAssetDragDropFrame()`
|
|
|
|
|
|
4. 渲染顶部 toolbar
|
|
|
|
|
|
5. 渲染左侧目录树与右侧浏览区
|
|
|
|
|
|
6. `FinalizeAssetDragDrop(manager)`
|
|
|
|
|
|
7. 在帧末执行 `m_deferredContextAction`
|
|
|
|
|
|
|
|
|
|
|
|
这里最关键的两个点是:
|
|
|
|
|
|
|
|
|
|
|
|
- Project 面板会显式声明当前焦点 route
|
|
|
|
|
|
- 拖放提交和上下文菜单命令都在本帧尾部统一收口,避免打断当前 UI 遍历
|
|
|
|
|
|
|
|
|
|
|
|
## 顶部工具栏
|
|
|
|
|
|
|
|
|
|
|
|
`RenderToolbar()` 当前负责两件事:
|
|
|
|
|
|
|
|
|
|
|
|
- 左侧显示当前 `ResourceManager` 的 project asset import 状态
|
|
|
|
|
|
- 右侧提供当前目录搜索框
|
|
|
|
|
|
|
|
|
|
|
|
导入状态文本和 tooltip 来自:
|
|
|
|
|
|
|
|
|
|
|
|
- `ResourceManager::GetProjectAssetImportStatus()`
|
|
|
|
|
|
- `AssetImportService::ImportStatusSnapshot`
|
|
|
|
|
|
|
|
|
|
|
|
搜索行为则比较克制:
|
|
|
|
|
|
|
|
|
|
|
|
- 只过滤 `manager.GetCurrentItems()` 返回的当前目录条目
|
|
|
|
|
|
- 匹配逻辑使用 `UI::SearchQuery::Matches(item->name)`
|
|
|
|
|
|
- 不做全项目搜索、类型过滤或标签过滤
|
2026-03-29 01:36:53 +08:00
|
|
|
|
|
|
|
|
|
|
## 左侧目录树
|
|
|
|
|
|
|
2026-04-04 01:02:57 +08:00
|
|
|
|
### 数据来源
|
|
|
|
|
|
|
|
|
|
|
|
目录树主要消费:
|
|
|
|
|
|
|
|
|
|
|
|
- `GetRootFolder()`
|
|
|
|
|
|
- `GetCurrentFolder()`
|
|
|
|
|
|
|
|
|
|
|
|
### 节点绘制与交互
|
|
|
|
|
|
|
|
|
|
|
|
每个目录节点当前通过 `UI::DrawTreeNode(...)` 绘制,并带有:
|
2026-03-29 01:36:53 +08:00
|
|
|
|
|
2026-04-04 01:02:57 +08:00
|
|
|
|
- 当前目录高亮
|
|
|
|
|
|
- 基于 `IsCurrentTreeBranch(...)` 的默认展开
|
|
|
|
|
|
- 自定义 folder icon prefix
|
2026-03-29 01:36:53 +08:00
|
|
|
|
|
2026-04-04 01:02:57 +08:00
|
|
|
|
当前支持的主要交互包括:
|
|
|
|
|
|
|
|
|
|
|
|
- 左键点击目录 -> `NavigateToFolder(folder)`
|
|
|
|
|
|
- 目录作为资源拖放目标
|
|
|
|
|
|
- 空白区右键弹出项目上下文菜单
|
|
|
|
|
|
|
|
|
|
|
|
若根目录不可用,则显示 `No Assets Folder` 空状态。
|
2026-03-29 01:36:53 +08:00
|
|
|
|
|
|
|
|
|
|
## 右侧浏览区
|
|
|
|
|
|
|
2026-04-04 01:02:57 +08:00
|
|
|
|
右侧浏览区分成两部分:
|
|
|
|
|
|
|
|
|
|
|
|
- `RenderBrowserHeader(manager)`
|
|
|
|
|
|
- `RenderBrowserPane(manager)` 的资源网格主体
|
|
|
|
|
|
|
|
|
|
|
|
### Breadcrumb
|
|
|
|
|
|
|
|
|
|
|
|
Header 当前通过 `UI::DrawToolbarBreadcrumbs(...)` 渲染路径,并消费:
|
|
|
|
|
|
|
|
|
|
|
|
- `manager.GetPathDepth()`
|
|
|
|
|
|
- `manager.GetPathName(index)`
|
|
|
|
|
|
- `manager.NavigateToIndex(index)`
|
2026-03-29 01:36:53 +08:00
|
|
|
|
|
2026-04-04 01:02:57 +08:00
|
|
|
|
因此目录导航当前主要依赖目录树和 breadcrumb,而不是单独的“后退按钮”。
|
2026-03-29 01:36:53 +08:00
|
|
|
|
|
2026-04-04 01:02:57 +08:00
|
|
|
|
### 资源网格
|
2026-03-29 01:36:53 +08:00
|
|
|
|
|
2026-04-04 01:02:57 +08:00
|
|
|
|
主体区域当前会:
|
2026-03-29 01:36:53 +08:00
|
|
|
|
|
2026-04-04 01:02:57 +08:00
|
|
|
|
1. 先基于搜索关键字计算 `visibleItems`
|
|
|
|
|
|
2. 动态估算卡片高度与列数
|
|
|
|
|
|
3. 逐项调用 `RenderAssetItem(...)`
|
|
|
|
|
|
4. 在帧末统一处理选中、打开与空白区清选
|
2026-03-29 01:36:53 +08:00
|
|
|
|
|
2026-04-04 01:02:57 +08:00
|
|
|
|
`RenderAssetItem(...)` 当前支持:
|
2026-03-29 01:36:53 +08:00
|
|
|
|
|
2026-04-04 01:02:57 +08:00
|
|
|
|
- 单击选中
|
|
|
|
|
|
- 双击打开
|
|
|
|
|
|
- 右键选中并弹出上下文菜单
|
|
|
|
|
|
- 纹理预览与图标 fallback
|
|
|
|
|
|
- 行内重命名
|
|
|
|
|
|
- 拖放源 / 目录拖放目标
|
2026-03-29 01:36:53 +08:00
|
|
|
|
|
2026-04-04 01:02:57 +08:00
|
|
|
|
若搜索结果为空,会显示:
|
2026-03-29 01:36:53 +08:00
|
|
|
|
|
2026-04-04 01:02:57 +08:00
|
|
|
|
- `No Search Results`
|
|
|
|
|
|
- `No assets match the current search`
|
2026-03-29 01:36:53 +08:00
|
|
|
|
|
2026-04-04 01:02:57 +08:00
|
|
|
|
## 上下文菜单与重命名
|
2026-03-29 01:36:53 +08:00
|
|
|
|
|
2026-04-04 01:02:57 +08:00
|
|
|
|
### 行内重命名
|
2026-03-29 01:36:53 +08:00
|
|
|
|
|
2026-04-04 01:02:57 +08:00
|
|
|
|
当前重命名由 `m_renameState` 驱动:
|
2026-03-29 01:36:53 +08:00
|
|
|
|
|
2026-04-04 01:02:57 +08:00
|
|
|
|
1. `BeginRename(item)` 激活编辑状态
|
|
|
|
|
|
2. `DrawInlineRenameFieldAt(...)` 覆盖卡片 label 区
|
|
|
|
|
|
3. 提交时调用 `CommitRename(...)`
|
|
|
|
|
|
4. 真正的重命名落到 `Commands::RenameAsset(...)`
|
2026-03-29 01:36:53 +08:00
|
|
|
|
|
2026-04-04 01:02:57 +08:00
|
|
|
|
若用户没有实际修改显示名称,`CommitRename(...)` 会直接结束重命名,而不会重复提交文件操作。
|
2026-03-29 01:36:53 +08:00
|
|
|
|
|
2026-04-04 01:02:57 +08:00
|
|
|
|
### 右键菜单
|
2026-03-29 01:36:53 +08:00
|
|
|
|
|
2026-04-04 01:02:57 +08:00
|
|
|
|
`DrawProjectContextMenu(...)` 当前支持:
|
2026-03-29 01:36:53 +08:00
|
|
|
|
|
2026-04-04 01:02:57 +08:00
|
|
|
|
- `Create -> Folder`
|
|
|
|
|
|
- `Create -> Material`
|
|
|
|
|
|
- `Show in Explore`
|
|
|
|
|
|
- `Open`
|
|
|
|
|
|
- `Delete`
|
|
|
|
|
|
- `Rename`
|
|
|
|
|
|
- `Copy Path`
|
2026-03-29 01:36:53 +08:00
|
|
|
|
|
2026-04-04 01:02:57 +08:00
|
|
|
|
其中创建资源采用 deferred action:
|
2026-03-29 01:36:53 +08:00
|
|
|
|
|
2026-04-04 01:02:57 +08:00
|
|
|
|
1. 必要时先导航到目标目录
|
|
|
|
|
|
2. 调用 `Commands::CreateFolder(...)` 或 `Commands::CreateMaterial(...)`
|
|
|
|
|
|
3. 新建成功后自动进入重命名状态
|
|
|
|
|
|
|
|
|
|
|
|
## 拖放与移动资源
|
|
|
|
|
|
|
|
|
|
|
|
当前拖放逻辑被收口成一套帧内状态机,而不是散落在每个 tile 里。
|
|
|
|
|
|
|
|
|
|
|
|
### 帧开始
|
|
|
|
|
|
|
|
|
|
|
|
`BeginAssetDragDropFrame()` 会:
|
|
|
|
|
|
|
|
|
|
|
|
- 清空 `m_assetDragDropState`
|
|
|
|
|
|
- 通过 `Actions::GetDraggedProjectAssetPath()` 识别当前拖拽源
|
|
|
|
|
|
|
|
|
|
|
|
### 目录作为 drop target
|
|
|
|
|
|
|
|
|
|
|
|
`RegisterFolderDropTarget(...)` 会:
|
|
|
|
|
|
|
|
|
|
|
|
- 只接受目录目标
|
|
|
|
|
|
- 校验 payload 类型为 `ProjectAssetPayloadType()`
|
|
|
|
|
|
- 用 `Commands::CanMoveAssetToFolder(...)` 判断是否合法
|
|
|
|
|
|
- 在真正投递时记录源路径与目标目录
|
|
|
|
|
|
|
|
|
|
|
|
### 帧结束统一提交
|
|
|
|
|
|
|
|
|
|
|
|
`FinalizeAssetDragDrop(...)` 会:
|
|
|
|
|
|
|
|
|
|
|
|
- 根据当前悬停是否合法切换鼠标样式
|
|
|
|
|
|
- 若本帧成功投递,则统一调用 `Commands::MoveAssetToFolder(...)`
|
|
|
|
|
|
|
|
|
|
|
|
这样可以避免在 UI 遍历过程中直接修改底层目录树。
|
|
|
|
|
|
|
|
|
|
|
|
## 与项目工作流命令的关系
|
|
|
|
|
|
|
|
|
|
|
|
`ProjectPanel` 当前直接消费的主要是资源级命令:
|
|
|
|
|
|
|
|
|
|
|
|
- `CreateFolder`
|
|
|
|
|
|
- `CreateMaterial`
|
|
|
|
|
|
- `DeleteAsset`
|
|
|
|
|
|
- `RenameAsset`
|
|
|
|
|
|
- `MoveAssetToFolder`
|
|
|
|
|
|
- `OpenAsset`
|
|
|
|
|
|
|
|
|
|
|
|
它**不**负责发起以下项目级入口:
|
|
|
|
|
|
|
|
|
|
|
|
- `Save Project`
|
|
|
|
|
|
- `Open Project...`
|
|
|
|
|
|
- `New Project...`
|
|
|
|
|
|
- `Rebuild Script Assemblies`
|
|
|
|
|
|
|
|
|
|
|
|
这些工作流分别由主菜单和命令层收口。
|
|
|
|
|
|
|
|
|
|
|
|
当外部命令触发 `RefreshCurrentFolder()` 时,面板会在下一帧反映新的目录树状态。
|
|
|
|
|
|
|
|
|
|
|
|
## 测试锚点
|
|
|
|
|
|
|
2026-04-04 01:05:04 +08:00
|
|
|
|
`tests/Editor/test_action_routing.cpp` 当前与这条资源链路直接相关的测试包括:
|
2026-04-04 01:02:57 +08:00
|
|
|
|
|
|
|
|
|
|
- `ProjectCommandsCreateFolderMoveAssetAndOpenFolderHelper`
|
|
|
|
|
|
- `ProjectCommandsCreateFolderUsesUniqueDefaultName`
|
|
|
|
|
|
- `ProjectCommandsRenameAssetUpdatesSelectionAndPreservesFileExtension`
|
|
|
|
|
|
- `ProjectCommandsRejectInvalidMoveTargets`
|
|
|
|
|
|
- `ProjectSelectionSurvivesRefreshWhenItemOrderChanges`
|
|
|
|
|
|
- `ProjectCommandsRejectMovingFolderIntoItsDescendant`
|
2026-03-29 01:36:53 +08:00
|
|
|
|
|
|
|
|
|
|
## 当前限制
|
|
|
|
|
|
|
2026-04-04 01:02:57 +08:00
|
|
|
|
- 搜索当前只覆盖当前目录的名称匹配。
|
|
|
|
|
|
- 目录树展开状态当前只保存在内存里。
|
|
|
|
|
|
- 拖拽预览 tooltip 被关闭,拖放反馈比较轻量。
|
|
|
|
|
|
- 资源上下文菜单仍偏基础,没有批量操作或复杂预览。
|
|
|
|
|
|
- 面板本身不是项目保存或脚本重建入口。
|
2026-03-27 14:40:29 +08:00
|
|
|
|
|
|
|
|
|
|
## 相关文档
|
|
|
|
|
|
|
2026-04-04 01:02:57 +08:00
|
|
|
|
- [Constructor](Constructor.md)
|
|
|
|
|
|
- [Initialize](Initialize.md)
|
|
|
|
|
|
- [Render](Render.md)
|
2026-03-27 14:40:29 +08:00
|
|
|
|
- [panels](../panels.md)
|
2026-04-04 01:02:57 +08:00
|
|
|
|
- [ProjectActionRouter](../../Actions/ProjectActionRouter/ProjectActionRouter.md)
|
|
|
|
|
|
- [ProjectCommands](../../Commands/ProjectCommands/ProjectCommands.md)
|
2026-03-29 01:36:53 +08:00
|
|
|
|
- [IProjectManager](../../Core/IProjectManager/IProjectManager.md)
|
2026-03-27 14:40:29 +08:00
|
|
|
|
- [ProjectManager](../../Managers/ProjectManager/ProjectManager.md)
|
|
|
|
|
|
- [AssetItem](../../Core/AssetItem/AssetItem.md)
|
2026-04-04 01:02:57 +08:00
|
|
|
|
- [Widgets](../../UI/Widgets/Widgets.md)
|