docs: sync api and planning docs
This commit is contained in:
@@ -6,44 +6,224 @@
|
||||
|
||||
**源文件**: `editor/src/Commands/ProjectCommands.h`
|
||||
|
||||
**描述**: 封装 Project 面板中与资源打开、目录创建、删除和移动相关的高层操作。
|
||||
**描述**: Project 工作流命令集合,封装项目切换与保存、资源浏览与文件操作,以及脚本程序集重建等高层动作。
|
||||
|
||||
## 概述
|
||||
|
||||
`ProjectCommands` 当前是一个很轻量的命令头,但它已经把 Project 面板最基础的资源管理语义集中起来:
|
||||
`ProjectCommands` 当前已经不只是“打开资源 + 建目录 + 删除/移动”的薄命令头。
|
||||
|
||||
- `CanOpenAsset`
|
||||
- `OpenAsset`
|
||||
- `CreateFolder`
|
||||
- `DeleteAsset`
|
||||
- `MoveAssetToFolder`
|
||||
按 `ProjectCommands.h` 的真实实现,它覆盖四组链路:
|
||||
|
||||
这让资源浏览面板不必直接操作底层 manager 细节。
|
||||
- 资源浏览与编辑命令
|
||||
- 项目文档保存命令
|
||||
- 脚本程序集重建
|
||||
- 项目切换与项目目录初始化
|
||||
|
||||
## 当前实现
|
||||
和 [ProjectPanel](../../panels/ProjectPanel/ProjectPanel.md) 的关系也很清楚:
|
||||
|
||||
- 资源能否打开,当前只看两种情况:目录,或 `item->type == "Scene"`
|
||||
- 打开目录时调用 `IProjectManager::NavigateToFolder`
|
||||
- 打开场景资源时转发到 [`LoadScene`](../SceneCommands/SceneCommands.md)
|
||||
- 创建目录和删除资源只做最小参数校验,然后委托给 `IProjectManager`
|
||||
- 资源拖入目录时会比较源路径与目标路径,避免把资源移动到自己身上
|
||||
- Panel 负责 UI 与交互采样
|
||||
- Commands 负责 guard、规则和 manager / application 调用
|
||||
|
||||
## 资源命令
|
||||
|
||||
### 打开资源
|
||||
|
||||
`CanOpenAsset(item)` 当前只在两种情况下返回 `true`:
|
||||
|
||||
- `item->isFolder`
|
||||
- `item->type == "Scene"`
|
||||
|
||||
`OpenAsset(context, item)` 则进一步区分:
|
||||
|
||||
- 文件夹: `context.GetProjectManager().NavigateToFolder(item)`
|
||||
- 场景资源: 转发到 [`LoadScene`](../SceneCommands/SceneCommands.md)
|
||||
|
||||
因此当前“打开资源”仍然是收敛语义,不会在这里直接承载脚本、材质或图片编辑器分发。
|
||||
|
||||
### 创建文件夹
|
||||
|
||||
`CreateFolder(projectManager, name)` 当前只做最基本校验:
|
||||
|
||||
- 名称不能为空
|
||||
|
||||
真正的唯一命名、落盘和刷新由 [`IProjectManager::CreateFolder`](../../Core/IProjectManager/IProjectManager.md) 负责。
|
||||
|
||||
### 创建材质
|
||||
|
||||
`CreateMaterial(projectManager, name)` 当前会:
|
||||
|
||||
1. 读取当前目录与根目录。
|
||||
2. 验证当前目录位于项目根目录内。
|
||||
3. 自动补 `.mat` 扩展名。
|
||||
4. 为重名文件追加数字后缀。
|
||||
5. 直接写出默认材质 JSON。
|
||||
6. `RefreshCurrentFolder()`。
|
||||
7. 定位新建条目并设为当前选中项。
|
||||
|
||||
因此 `ProjectPanel` 右键菜单里的 `Create -> Material` 已经是落地命令,不再只是 UI 预留位。
|
||||
|
||||
### 删除 / 移动 / 重命名
|
||||
|
||||
这三类命令当前都只是把高层工作流规则收口后,再委托给 `IProjectManager`:
|
||||
|
||||
- `DeleteAsset(...)` -> `projectManager.DeleteItem(...)`
|
||||
- `MoveAssetToFolder(...)` -> `projectManager.MoveItem(...)`
|
||||
- `RenameAsset(...)` -> `projectManager.RenameItem(...)`
|
||||
|
||||
其中 `CanMoveAssetToFolder(...)` 额外承担了较完整的安全校验,包括:
|
||||
|
||||
- 源与目标都必须位于项目根目录内
|
||||
- 不能移动项目根目录本身
|
||||
- 不能把目录移动到自己的子目录
|
||||
- 不能覆盖同名目标
|
||||
|
||||
## 项目保存命令
|
||||
|
||||
### `EnsureProjectStructure(projectPath)`
|
||||
|
||||
当前会确保以下目录存在:
|
||||
|
||||
- `Assets/Scenes`
|
||||
- `.xceditor`
|
||||
|
||||
### `BuildProjectDescriptor(context)`
|
||||
|
||||
当前会结合:
|
||||
|
||||
- `context.GetProjectPath()`
|
||||
- `context.GetSceneManager().GetCurrentScenePath()`
|
||||
|
||||
构建项目描述;当没有有效当前场景时,回退到:
|
||||
|
||||
- `Assets/Scenes/Main.xc`
|
||||
|
||||
### `SaveProject(context)`
|
||||
|
||||
这是当前项目保存链路的总入口,主要流程是:
|
||||
|
||||
1. 检查当前 runtime mode 是否允许文档编辑。
|
||||
2. `EnsureProjectStructure(context.GetProjectPath())`。
|
||||
3. 通过 `SaveDirtySceneWithFallback(...)` 保存当前场景,必要时回退到 `Assets/Scenes/Main.xc`。
|
||||
4. `context.GetProjectManager().RefreshCurrentFolder()`。
|
||||
5. `Application::Get().SaveProjectState()`。
|
||||
6. `SaveProjectDescriptor(context)`。
|
||||
|
||||
这说明 `ProjectCommands` 当前已经超出“Project 面板本地命令”的范畴,开始承担项目文档生命周期本身。
|
||||
|
||||
还要注意这个顺序的真实含义:
|
||||
|
||||
- `RefreshCurrentFolder()` 只会同步 `Assets` 浏览树
|
||||
- `SaveProjectState()` 和项目描述文件会落在 `Assets` 之外的项目级位置
|
||||
|
||||
因此“保存项目”成功并不等于 `ProjectPanel` 一定出现明显可见变化;面板刷新更多是在保证项目资源视图和磁盘状态保持一致。
|
||||
|
||||
## 脚本程序集重建
|
||||
|
||||
### `CanRebuildScriptAssemblies(context)`
|
||||
|
||||
当前 guard 很明确:
|
||||
|
||||
- 文档编辑必须允许
|
||||
- 项目路径不能为空
|
||||
|
||||
### `RebuildScriptAssemblies(context)`
|
||||
|
||||
这是 `Scripts -> Rebuild Script Assemblies` 菜单项的命令层入口。
|
||||
|
||||
当前流程是:
|
||||
|
||||
1. 先走 `CanRebuildScriptAssemblies(context)`。
|
||||
2. 调用 [`Application::RebuildScriptingAssemblies`](../../Application/RebuildScriptingAssemblies.md)。
|
||||
3. 若重建成功,执行 `context.GetProjectManager().RefreshCurrentFolder()`。
|
||||
4. 把布尔结果直接返回给调用方。
|
||||
|
||||
因此它的职责边界是:
|
||||
|
||||
- `ProjectCommands` 负责 guard 和后置刷新。
|
||||
- `Application` 负责真正的程序集构建。
|
||||
|
||||
这里有两个很实际的调用语义:
|
||||
|
||||
- 返回 `false` 既可能是 guard 不通过,也可能是下游脚本构建失败。
|
||||
- 即使返回 `true`,`RefreshCurrentFolder()` 也不代表 `ProjectPanel` 一定会出现新条目,因为脚本程序集产物主要位于 `<Project>/Library/ScriptAssemblies`,不在 `Assets` 投影视图内。
|
||||
|
||||
## 项目切换与目录选择
|
||||
|
||||
### `SwitchProject(context, projectPath)`
|
||||
|
||||
当前项目切换主流程是:
|
||||
|
||||
1. 要求允许文档编辑。
|
||||
2. 要求目标路径非空。
|
||||
3. 通过 `SceneEditorUtils::ConfirmSceneSwitch(context)` 处理场景切换确认。
|
||||
4. `EnsureProjectStructure(projectPath)`。
|
||||
5. `Application::Get().SwitchProject(projectPath)`。
|
||||
6. 写回 `context.SetProjectPath(projectPath)`。
|
||||
7. `context.GetProjectManager().Initialize(projectPath)`。
|
||||
8. `context.GetSceneManager().LoadStartupScene(projectPath)`。
|
||||
9. 清空选择与 undo 历史。
|
||||
10. `RefreshCurrentFolder()`。
|
||||
11. 若成功加载启动场景,则 `SaveProjectDescriptor(context)`。
|
||||
|
||||
这说明 `ProjectCommands` 现在也是 Project 生命周期切换的统一入口,而不是只服务 `ProjectPanel`。
|
||||
|
||||
这里的重置动作也很关键:
|
||||
|
||||
- `ClearSelection()` 保证旧项目里的资源 / 实体选择不会泄漏到新项目上下文
|
||||
- `ClearHistory()` 保证旧项目的撤销栈不会跨项目复用
|
||||
|
||||
### `NewProjectWithDialog(context)` / `OpenProjectWithDialog(context)`
|
||||
|
||||
这两个 helper 都通过 `FileDialogUtils::PickFolderDialog(...)` 选择目录,然后回到 `SwitchProject(...)`。
|
||||
|
||||
也就是说:
|
||||
|
||||
- 对话框归它们负责
|
||||
- 真正的切换规则仍收口在 `SwitchProject(...)`
|
||||
|
||||
## 设计说明
|
||||
|
||||
现在这层看起来很薄,但它依然有存在价值。
|
||||
原因在于 Project 面板的 UI 以后很可能持续变化,而“什么资源能打开、什么动作合法、如何调用 manager”这些规则应该集中在一个稳定位置。
|
||||
当前 `ProjectCommands` 的设计方向很清楚:
|
||||
|
||||
这也是商业编辑器常见的演进路径:先用轻量命令层收口,等资源类型和导入系统成熟后,再逐渐扩展成完整 asset workflow。
|
||||
- 把资源管理规则集中化
|
||||
- 把项目文档保存链路集中化
|
||||
- 把项目维护类动作从菜单 / 面板层抽离出来
|
||||
- 让 `ProjectPanel` 只消费命令,而不自己拼文件系统规则
|
||||
|
||||
当前最值得明确的一点是:旧文档里提到的项目级批量场景迁移入口已经不在这个头文件里,当前保留下来的项目维护动作只有:
|
||||
|
||||
- 保存项目
|
||||
- 重建脚本程序集
|
||||
- 切换项目 / 新建项目 / 打开项目
|
||||
|
||||
这才是当前源码里的真实分层。
|
||||
|
||||
## 测试锚点
|
||||
|
||||
`tests/Editor/test_action_routing.cpp` 当前直接覆盖或锚定了这组命令的关键行为:
|
||||
|
||||
- `ProjectCommandsReportWhenScriptAssembliesCanBeRebuilt`
|
||||
- `ProjectCommandsCreateFolderMoveAssetAndOpenFolderHelper`
|
||||
- `ProjectCommandsCreateFolderUsesUniqueDefaultName`
|
||||
- `ProjectCommandsRenameAssetUpdatesSelectionAndPreservesFileExtension`
|
||||
- `ProjectCommandsRejectInvalidMoveTargets`
|
||||
- `ProjectCommandsRejectMovingFolderIntoItsDescendant`
|
||||
|
||||
## 当前限制
|
||||
|
||||
- 只能打开文件夹和场景资源
|
||||
- 没有资源重命名、复制粘贴、导入和重新导入
|
||||
- 删除和移动操作没有事务、确认框或冲突解决策略
|
||||
- 资源打开仍然只覆盖文件夹和场景。
|
||||
- 创建材质当前写的是内置默认 JSON 骨架,没有更复杂模板选择。
|
||||
- 删除、移动、重命名仍然没有事务预览或冲突解决 UI。
|
||||
- 资源复制 / 粘贴 / 重新导入仍未形成命令层协议。
|
||||
- 脚本重建当前只返回布尔值,不负责弹窗反馈或进度 UI;主菜单层也不会消费这个返回值。
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [Commands](../Commands.md)
|
||||
- [MainMenuActionRouter](../../Actions/MainMenuActionRouter/MainMenuActionRouter.md)
|
||||
- [ProjectPanel](../../panels/ProjectPanel/ProjectPanel.md)
|
||||
- [ProjectActionRouter](../../Actions/ProjectActionRouter/ProjectActionRouter.md)
|
||||
- [IProjectManager](../../Core/IProjectManager/IProjectManager.md)
|
||||
- [ProjectManager](../../Managers/ProjectManager/ProjectManager.md)
|
||||
- [SceneCommands](../SceneCommands/SceneCommands.md)
|
||||
- [Application](../../Application/Application.md)
|
||||
|
||||
Reference in New Issue
Block a user