docs: sync project browser docs
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
# ProjectManager::CreateFolder
|
||||
|
||||
```cpp
|
||||
AssetItemPtr CreateFolder(const std::string& name);
|
||||
```
|
||||
|
||||
## 当前行为
|
||||
|
||||
`CreateFolder(...)` 当前在“当前目录”下创建新文件夹,而不是在任意传入路径下创建。
|
||||
|
||||
它会:
|
||||
|
||||
1. trim 输入名称
|
||||
2. 拒绝空名、`.`、`..` 和 Windows 非法文件名字符
|
||||
3. 在当前目录下计算唯一目标名
|
||||
4. 执行 `create_directory(...)`
|
||||
5. 刷新目录树
|
||||
6. 查回新建目录对应的 `AssetItem`
|
||||
7. 自动把它设为当前选中项
|
||||
|
||||
## 唯一命名规则
|
||||
|
||||
若目标目录已存在,当前会追加数字后缀:
|
||||
|
||||
- `New Folder`
|
||||
- `New Folder 1`
|
||||
- `New Folder 2`
|
||||
|
||||
这与 `tests/editor/test_action_routing.cpp` 的 `ProjectCommandsCreateFolderUsesUniqueDefaultName` 一致。
|
||||
|
||||
## 返回值语义
|
||||
|
||||
- 创建并刷新成功时,返回新目录对应的 `AssetItemPtr`
|
||||
- 参数非法、目录创建失败或刷新后无法重新解析该目录时,返回 `nullptr`
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [ProjectManager](ProjectManager.md)
|
||||
- [RefreshCurrentFolder](RefreshCurrentFolder.md)
|
||||
- [ProjectCommands](../../Commands/ProjectCommands/ProjectCommands.md)
|
||||
@@ -0,0 +1,50 @@
|
||||
# ProjectManager::DeleteItem
|
||||
|
||||
```cpp
|
||||
bool DeleteItem(const std::string& fullPath);
|
||||
```
|
||||
|
||||
## 当前行为
|
||||
|
||||
`DeleteItem(...)` 只允许删除当前项目 `Assets` 根目录之下的条目。
|
||||
|
||||
当前会依次校验:
|
||||
|
||||
- `fullPath` 非空
|
||||
- `m_rootFolder` 已存在
|
||||
- 目标路径存在
|
||||
- 目标路径位于当前项目根目录之下
|
||||
- 目标路径不是根目录本身
|
||||
|
||||
通过后才会执行真正删除。
|
||||
|
||||
## 删除语义
|
||||
|
||||
当前删除动作包括两步:
|
||||
|
||||
1. `fs::remove_all(itemPath)`
|
||||
2. 额外删除同名 `.meta` sidecar
|
||||
|
||||
然后:
|
||||
|
||||
- 若当前选中的正是这个路径,会先清空选择
|
||||
- 最后调用 [RefreshCurrentFolder](RefreshCurrentFolder.md)
|
||||
|
||||
删除 sidecar 的目的是让资源标识与导入元数据和资源本体一起消失,避免删掉文件后仍残留孤立的 `.meta`。
|
||||
|
||||
## 返回值语义
|
||||
|
||||
- 删除并刷新成功时返回 `true`
|
||||
- 任一前置校验失败或文件系统异常时返回 `false`
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 当前不会弹出冲突确认或预览
|
||||
- 删除目录时依赖 `remove_all(...)` 递归删除内容
|
||||
- 只要路径越出项目根目录,当前就会直接拒绝
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [ProjectManager](ProjectManager.md)
|
||||
- [MoveItem](MoveItem.md)
|
||||
- [RenameItem](RenameItem.md)
|
||||
@@ -0,0 +1,41 @@
|
||||
# ProjectManager::Initialize
|
||||
|
||||
```cpp
|
||||
void Initialize(const std::string& projectPath);
|
||||
```
|
||||
|
||||
## 当前行为
|
||||
|
||||
`Initialize(...)` 会把传入路径记为 `m_projectPath`,然后把 `<projectPath>/Assets` 作为当前项目浏览根目录。
|
||||
|
||||
按当前实现:
|
||||
|
||||
1. 计算 `<project>/Assets`
|
||||
2. 若不存在,则创建:
|
||||
- `Assets/`
|
||||
- `Assets/Scenes/`
|
||||
3. 调用内部 `ScanDirectory(...)` 递归构建根目录树
|
||||
4. 把根节点名称强制设为 `"Assets"`
|
||||
5. 重置 `m_path` 为只包含根目录的一层
|
||||
6. 清空当前选择
|
||||
|
||||
## 失败退化
|
||||
|
||||
若扫描或建目录过程中抛异常,当前不会把初始化直接判成失败,而是退化为:
|
||||
|
||||
- 构造一个最小可用的 `Folder` 根节点
|
||||
- 仍把名字设为 `"Assets"`
|
||||
- 仍重置路径栈与当前选择
|
||||
|
||||
也就是说,`ProjectManager` 更偏向“尽量给 ProjectPanel 一个可工作的空根目录”,而不是把异常往上抛。
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 当前只保证 `Assets/Scenes`,不会自动生成旧文档里提到的一整套示例子目录。
|
||||
- 初始化后当前位置总是根目录,不会保留旧会话中的最后浏览路径。
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [ProjectManager](ProjectManager.md)
|
||||
- [RefreshCurrentFolder](RefreshCurrentFolder.md)
|
||||
- [ProjectPanel](../../panels/ProjectPanel/ProjectPanel.md)
|
||||
49
docs/api/XCEngine/Editor/Managers/ProjectManager/MoveItem.md
Normal file
49
docs/api/XCEngine/Editor/Managers/ProjectManager/MoveItem.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# ProjectManager::MoveItem
|
||||
|
||||
```cpp
|
||||
bool MoveItem(const std::string& sourceFullPath, const std::string& destFolderFullPath);
|
||||
```
|
||||
|
||||
## 当前行为
|
||||
|
||||
`MoveItem(...)` 当前只支持“把一个项目内条目移动到另一个项目内目录”。
|
||||
|
||||
它会校验:
|
||||
|
||||
- 源路径和目标目录路径都非空
|
||||
- `m_rootFolder` 已存在
|
||||
- 源路径存在
|
||||
- 目标路径存在且是目录
|
||||
- 源和目标都位于项目根目录之下
|
||||
- 源路径不是根目录本身
|
||||
- 若源是目录,目标不能位于它自己的子树里
|
||||
- 目标最终路径不能与源路径相同
|
||||
- 目标最终路径不能已存在同名条目
|
||||
|
||||
## 成功路径
|
||||
|
||||
校验通过后,当前会:
|
||||
|
||||
1. 把目标路径拼成 `destFolder / source.filename()`
|
||||
2. 对资源本体执行 `fs::rename(sourcePath, destPath)`
|
||||
3. 若存在同名 `.meta` sidecar,则同步移动 `.meta`
|
||||
4. 调用 [RefreshCurrentFolder](RefreshCurrentFolder.md)
|
||||
|
||||
同步移动 sidecar 是当前资源移动行为里很重要的一部分,因为它让导入元数据和资源路径保持一致,不会把 `.meta` 留在旧目录。
|
||||
|
||||
## 返回值语义
|
||||
|
||||
- 移动成功并刷新完成时返回 `true`
|
||||
- 任一校验失败或文件系统异常时返回 `false`
|
||||
|
||||
## 当前限制
|
||||
|
||||
- 当前不做自动重命名避让;目标已有同名条目会直接失败
|
||||
- 当前不会主动保留被移动条目的选择状态;若路径变化导致旧 `m_selectedItemPath` 失效,刷新后选择可能被清掉
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [ProjectManager](ProjectManager.md)
|
||||
- [DeleteItem](DeleteItem.md)
|
||||
- [RenameItem](RenameItem.md)
|
||||
- [ProjectCommands](../../Commands/ProjectCommands/ProjectCommands.md)
|
||||
@@ -6,35 +6,203 @@
|
||||
|
||||
**源文件**: `editor/src/Managers/ProjectManager.h`
|
||||
|
||||
**描述**: `IProjectManager` 的默认实现,负责扫描项目 `Assets` 目录、维护面包屑路径,并支持基础文件夹与文件操作。
|
||||
**描述**: `IProjectManager` 的默认实现,负责把项目 `Assets` 目录扫描成 `AssetItem` 树,并提供路径导航、选择同步与基础文件操作。
|
||||
|
||||
## 概述
|
||||
|
||||
`ProjectManager` 当前本质上是一个面向 `Assets` 目录的轻量文件系统浏览器。
|
||||
`ProjectManager` 当前不是 `AssetDatabase`,也不是资源导入服务;但它也已经不只是“轻量文件浏览器”。
|
||||
|
||||
它负责:
|
||||
按当前实现,它承担三类稳定职责:
|
||||
|
||||
- 初始化项目目录结构
|
||||
- 扫描目录并构建 `AssetItem` 树
|
||||
- 维护当前路径栈
|
||||
- 创建文件夹、删除项、移动项
|
||||
- 把 `<Project>/Assets` 扫描为 `AssetItem` 树,供 [ProjectPanel](../../panels/ProjectPanel/ProjectPanel.md) 消费
|
||||
- 维护浏览路径、当前选择与刷新后的状态恢复
|
||||
- 处理创建、删除、移动、重命名等基础文件系统操作
|
||||
|
||||
## 当前实现说明
|
||||
因此它更准确的定位是:
|
||||
|
||||
- `Initialize(projectPath)` 会确保 `<project>/Assets` 以及若干默认子目录存在。
|
||||
- 若第一次初始化发现目录不存在,会自动创建 `Textures/Models/Scripts/Materials/Scenes` 等目录,并生成少量示例文件。
|
||||
- `GetCurrentItems()` 返回当前路径末尾节点的 `children`。
|
||||
- 目录扫描时,文件夹会排在文件前面,并按名字排序。
|
||||
- 资源类型当前按扩展名启发式推断,例如 `.png -> Texture`、`.fbx -> Model`、`.cs/.cpp/.h -> Script`。
|
||||
- editor 侧项目文件系统投影层
|
||||
- Project Browser 的默认执行层
|
||||
|
||||
## 当前实现边界
|
||||
而不是 engine 侧资产数据库本身。
|
||||
|
||||
- 目前是实时扫描树,不是文件系统监听模式。
|
||||
- 异常处理大多是吞掉异常并保持当前状态,错误反馈很弱。
|
||||
- 类型判断完全靠扩展名映射,不依赖资源导入数据库。
|
||||
## 内部状态模型
|
||||
|
||||
当前长期持有的核心状态只有四类:
|
||||
|
||||
| 状态 | 作用 |
|
||||
|------|------|
|
||||
| `m_rootFolder` | 当前 `Assets` 根节点。 |
|
||||
| `m_path` | 当前浏览路径的目录节点栈,也是面包屑来源。 |
|
||||
| `m_selectedItemPath` | 当前选中项的完整路径字符串。 |
|
||||
| `m_projectPath` | 当前项目根目录。 |
|
||||
|
||||
这套设计的直接收益是:
|
||||
|
||||
- 刷新树时,不需要保留旧 `AssetItem` 指针身份
|
||||
- 只要路径还能重新解析,当前目录和当前选中项就能在重建后恢复
|
||||
|
||||
## 扫描与类型推断
|
||||
|
||||
### 根目录初始化
|
||||
|
||||
[Initialize](Initialize.md) 当前固定把 `<project>/Assets` 当作根目录:
|
||||
|
||||
- 若目录不存在,会先创建 `Assets/` 和 `Assets/Scenes/`
|
||||
- 根节点名称固定重写为 `Assets`
|
||||
- 初始化后 `m_path` 只包含根节点
|
||||
|
||||
它不会额外创建旧文档里提到的演示子目录,也不会在这里预热资产数据库。
|
||||
|
||||
### 递归扫描
|
||||
|
||||
`ScanDirectory(...)` 当前会:
|
||||
|
||||
- 遍历目录项
|
||||
- 对子目录递归构建子 `AssetItem`
|
||||
- 跳过显示层面的 `.meta` 文件
|
||||
- 把目录排在文件前面,再按名称排序
|
||||
|
||||
这意味着 `ProjectPanel` 看到的是“过滤掉 `.meta` 侧车文件后的浏览树”,而不是磁盘上的逐项镜像。
|
||||
|
||||
### 类型推断
|
||||
|
||||
文件项当前主要按扩展名启发式推断:
|
||||
|
||||
| 扩展名 | 当前类型 |
|
||||
|------|------|
|
||||
| 图片扩展名 | `Texture` |
|
||||
| `.fbx/.obj/.gltf/.glb` | `Model` |
|
||||
| `.cs/.cpp/.h` | `Script` |
|
||||
| `.mat` | `Material` |
|
||||
| `.xc/.unity/.scene` | `Scene` |
|
||||
| `.prefab` | `Prefab` |
|
||||
| 其他 | `File` |
|
||||
|
||||
同时还会记录:
|
||||
|
||||
- `extensionLower`
|
||||
- `isImageAsset`
|
||||
- `canUseImagePreview`
|
||||
|
||||
供 `ProjectPanel` 的图标与缩略图策略使用。
|
||||
|
||||
## 路径导航与选择恢复
|
||||
|
||||
### 路径导航
|
||||
|
||||
当前路径相关公开 API 基本都围绕 `m_path` 工作:
|
||||
|
||||
- `NavigateToFolder(...)`
|
||||
- 重新解析从根到目标目录的完整路径
|
||||
- 成功后清空当前选择
|
||||
- `NavigateBack()`
|
||||
- 只在 `m_path.size() > 1` 时后退
|
||||
- 后退后清空当前选择
|
||||
- `NavigateToIndex(index)`
|
||||
- 直接截断面包屑到指定层级
|
||||
- 同样会清空当前选择
|
||||
|
||||
`GetCurrentPath()` 始终返回从 `"Assets"` 开始的逻辑路径,而不是磁盘绝对路径。
|
||||
|
||||
### 选择恢复
|
||||
|
||||
`ProjectManager` 当前把选择保存成 `m_selectedItemPath`,而不是保留旧 `AssetItemPtr`。
|
||||
|
||||
因此:
|
||||
|
||||
- `RefreshCurrentFolder()` 重建目录树后,只要同一路径仍存在,选择可以恢复
|
||||
- 若原路径不存在,则 `SyncSelection()` 自动清除选择
|
||||
|
||||
这也是 `ProjectSelectionSurvivesRefreshWhenItemOrderChanges` 能成立的根本原因。
|
||||
|
||||
## 基础文件操作
|
||||
|
||||
当前真正值得文档化的,不是 getter / setter,而是这几组带规则的写操作。
|
||||
|
||||
### 创建目录
|
||||
|
||||
[CreateFolder](CreateFolder.md) 当前会:
|
||||
|
||||
- trim 名称
|
||||
- 拒绝空名、`.`、`..` 和 Windows 非法文件名字符
|
||||
- 在当前目录下生成唯一目录名,如 `New Folder 1`
|
||||
- 刷新树并自动选中新目录
|
||||
|
||||
### 删除资源
|
||||
|
||||
[DeleteItem](DeleteItem.md) 当前会:
|
||||
|
||||
- 拒绝空路径、根目录本身和项目根之外的路径
|
||||
- 删除目标本体
|
||||
- 额外删除同名 `.meta` sidecar
|
||||
- 必要时清空选择
|
||||
- 刷新目录树
|
||||
|
||||
这里删除 `.meta` 并不是“顺手清理垃圾文件”,而是为了避免资源本体被移除后仍残留旧 GUID / 导入元数据。
|
||||
|
||||
### 移动资源
|
||||
|
||||
[MoveItem](MoveItem.md) 当前会:
|
||||
|
||||
- 要求源和目标都位于当前 `Assets` 根目录之下
|
||||
- 要求目标是已存在目录
|
||||
- 禁止移动根目录本身
|
||||
- 禁止把目录移动到自己的子目录
|
||||
- 禁止覆盖已有同名目标
|
||||
- 成功后同步移动 `.meta` sidecar
|
||||
|
||||
同步移动 sidecar 的意义同样是保持资源身份与导入配置跟随资源本体一起迁移,而不是把 `.meta` 留在旧路径。
|
||||
|
||||
### 重命名资源
|
||||
|
||||
[RenameItem](RenameItem.md) 当前会:
|
||||
|
||||
- trim 并校验新名字
|
||||
- 对文件项在“不写扩展名”时自动保留原扩展名
|
||||
- 支持 Windows 下 case-only rename
|
||||
- 成功后同步重命名 `.meta` sidecar
|
||||
- 若被重命名项正好是当前选中项,会同步更新 `m_selectedItemPath`
|
||||
|
||||
因此当前重命名语义已经不只是 UI 上改显示名,而是显式维护资源路径、sidecar 和选择状态的一致性。
|
||||
|
||||
## 公开方法
|
||||
|
||||
| 方法 | 说明 |
|
||||
|------|------|
|
||||
| [Initialize](Initialize.md) | 初始化项目根、确保 `Assets/Scenes` 存在并建立根目录树。 |
|
||||
| [RefreshCurrentFolder](RefreshCurrentFolder.md) | 重建目录树并尽量保留当前路径与当前选择。 |
|
||||
| [CreateFolder](CreateFolder.md) | 在当前目录创建唯一命名的新文件夹。 |
|
||||
| [DeleteItem](DeleteItem.md) | 删除项目内资源或目录,并移除同名 `.meta` sidecar。 |
|
||||
| [MoveItem](MoveItem.md) | 在项目目录内移动资源或目录,并同步移动 `.meta` sidecar。 |
|
||||
| [RenameItem](RenameItem.md) | 重命名资源或目录,支持保留扩展名与 case-only rename。 |
|
||||
|
||||
其余 getter / setter / 导航方法当前语义较直接,类型页中已概述,不再逐个拆页。
|
||||
|
||||
## 测试与行为锚点
|
||||
|
||||
和当前 `ProjectManager` 直接相关的测试锚点主要在 `tests/editor/test_action_routing.cpp`:
|
||||
|
||||
- `ProjectCommandsCreateFolderMoveAssetAndOpenFolderHelper`
|
||||
- `ProjectCommandsCreateFolderUsesUniqueDefaultName`
|
||||
- `ProjectCommandsRenameAssetUpdatesSelectionAndPreservesFileExtension`
|
||||
- `ProjectCommandsRejectInvalidMoveTargets`
|
||||
- `ProjectSelectionSurvivesRefreshWhenItemOrderChanges`
|
||||
- `ProjectCommandsRejectMovingFolderIntoItsDescendant`
|
||||
|
||||
这些测试虽然多数通过 `ProjectCommands` 外层 helper 进入,但已经把 `ProjectManager` 的真实文件系统行为钉得比较牢。
|
||||
|
||||
## 当前限制
|
||||
|
||||
- 当前仍是主动扫描树,不是文件系统监听器模型。
|
||||
- 类型识别仍然是扩展名启发式,不直接查询资产数据库。
|
||||
- 目录扫描和大部分文件系统错误当前都以“吞异常并保持可用”为主,诊断能力较弱。
|
||||
- `.meta` 文件只在变更操作时被显式跟随处理,不会单独显示在浏览树里。
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [Managers](../Managers.md)
|
||||
- [IProjectManager](../../Core/IProjectManager/IProjectManager.md)
|
||||
- [ProjectCommands](../../Commands/ProjectCommands/ProjectCommands.md)
|
||||
- [ProjectPanel](../../panels/ProjectPanel/ProjectPanel.md)
|
||||
- [AssetItem](../../Core/AssetItem/AssetItem.md)
|
||||
- [AssetDatabase](../../../Core/Asset/AssetDatabase/AssetDatabase.md)
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
# ProjectManager::RefreshCurrentFolder
|
||||
|
||||
```cpp
|
||||
void RefreshCurrentFolder();
|
||||
```
|
||||
|
||||
## 当前行为
|
||||
|
||||
这个方法当前不是“只刷新当前目录 children 列表”,而是调用内部 `RebuildTreePreservingPath()` 重建整棵 `Assets` 树,然后尽量恢复当前浏览状态。
|
||||
|
||||
内部主要流程是:
|
||||
|
||||
1. 记录旧的目录路径栈 `m_path`
|
||||
2. 重新从 `<project>/Assets` 扫描整棵树
|
||||
3. 重新解析旧路径栈,尽量恢复当前面包屑位置
|
||||
4. 通过 `SyncSelection()` 重新校验当前 `m_selectedItemPath`
|
||||
|
||||
## 保留语义
|
||||
|
||||
- 若原浏览路径中的每一级目录仍然存在,则当前目录会被保留
|
||||
- 若中间某一级目录已不存在,则恢复过程会在该层停止
|
||||
- 若当前选中项路径仍能在当前目录项中重新解析,选择会保留
|
||||
- 若当前选中项已不存在,则选择会被清空
|
||||
|
||||
这也是当前刷新后即使目录项顺序变化,选择仍可恢复的原因。
|
||||
|
||||
## 失败语义
|
||||
|
||||
当前实现把异常吞掉,因此:
|
||||
|
||||
- 刷新失败时不会抛异常
|
||||
- 也不会提供错误返回值
|
||||
- 上层看到的通常是“保持旧状态”
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [ProjectManager](ProjectManager.md)
|
||||
- [Initialize](Initialize.md)
|
||||
@@ -0,0 +1,66 @@
|
||||
# ProjectManager::RenameItem
|
||||
|
||||
```cpp
|
||||
bool RenameItem(const std::string& sourceFullPath, const std::string& newName);
|
||||
```
|
||||
|
||||
## 当前行为
|
||||
|
||||
`RenameItem(...)` 当前支持目录与文件重命名,但文件和目录的规则并不完全一样。
|
||||
|
||||
前置校验包括:
|
||||
|
||||
- `sourceFullPath` 非空
|
||||
- `m_rootFolder` 已存在
|
||||
- 新名字在 trim 后仍非空
|
||||
- 新名字不是 `.` / `..`
|
||||
- 新名字不包含 Windows 非法字符
|
||||
- 源路径存在且位于项目根目录之下
|
||||
- 源路径不是根目录本身
|
||||
|
||||
## 文件与目录的差异
|
||||
|
||||
### 目录
|
||||
|
||||
- 目录直接使用 trim 后的新名字
|
||||
|
||||
### 文件
|
||||
|
||||
当前会先走内部 `BuildRenamedEntryName(...)`:
|
||||
|
||||
- 若新名字本身已经带扩展名,就直接使用该文件名
|
||||
- 若新名字没带扩展名,则自动保留原文件扩展名
|
||||
|
||||
这也是 `ProjectCommandsRenameAssetUpdatesSelectionAndPreservesFileExtension` 这个测试成立的原因。
|
||||
|
||||
## case-only rename
|
||||
|
||||
Windows 下大小写不敏感,直接把 `foo.txt` 改成 `Foo.txt` 往往不能按普通重命名处理。
|
||||
|
||||
当前实现专门走了 `RenamePathCaseAware(...)`:
|
||||
|
||||
- 普通路径变化时直接 `fs::rename(...)`
|
||||
- 仅大小写变化时,先改到一个临时路径,再改到目标路径
|
||||
|
||||
因此 case-only rename 当前是被显式支持的。
|
||||
|
||||
## 成功路径
|
||||
|
||||
真正重命名成功后,当前还会:
|
||||
|
||||
1. 同步重命名 `.meta` sidecar
|
||||
2. 若当前选中的正是原路径,则把 `m_selectedItemPath` 更新到新路径
|
||||
3. 调用 [RefreshCurrentFolder](RefreshCurrentFolder.md)
|
||||
|
||||
这意味着当前实现维护的是“资源文件 + sidecar + 当前选择”三者一致性,而不只是单纯改一个文件名。
|
||||
|
||||
## 返回值语义
|
||||
|
||||
- 重命名成功并刷新完成时返回 `true`
|
||||
- 任一校验失败、目标名非法或文件系统异常时返回 `false`
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [ProjectManager](ProjectManager.md)
|
||||
- [MoveItem](MoveItem.md)
|
||||
- [DeleteItem](DeleteItem.md)
|
||||
Reference in New Issue
Block a user