Files
XCEngine/docs/api/XCEngine/Editor/Commands/ProjectCommands/ProjectCommands.md

230 lines
8.0 KiB
Markdown
Raw Normal View History

2026-03-27 14:40:29 +08:00
# ProjectCommands
**命名空间**: `XCEngine::Editor::Commands`
**类型**: `header-helper`
**源文件**: `editor/src/Commands/ProjectCommands.h`
2026-04-08 16:07:03 +08:00
**描述**: Project 工作流命令集合,封装项目切换与保存、资源浏览与文件操作,以及脚本程序集重建等高层动作。
2026-03-27 14:40:29 +08:00
## 概述
2026-04-08 16:07:03 +08:00
`ProjectCommands` 当前已经不只是“打开资源 + 建目录 + 删除/移动”的薄命令头。
2026-03-27 14:40:29 +08:00
2026-04-08 16:07:03 +08:00
`ProjectCommands.h` 的真实实现,它覆盖四组链路:
2026-03-27 14:40:29 +08:00
2026-04-08 16:07:03 +08:00
- 资源浏览与编辑命令
- 项目文档保存命令
- 脚本程序集重建
- 项目切换与项目目录初始化
2026-03-27 14:40:29 +08:00
2026-04-08 16:07:03 +08:00
和 [ProjectPanel](../../panels/ProjectPanel/ProjectPanel.md) 的关系也很清楚:
2026-03-27 14:40:29 +08:00
2026-04-08 16:07:03 +08:00
- 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(...)`
2026-03-27 14:40:29 +08:00
## 设计说明
2026-04-08 16:07:03 +08:00
当前 `ProjectCommands` 的设计方向很清楚:
- 把资源管理规则集中化
- 把项目文档保存链路集中化
- 把项目维护类动作从菜单 / 面板层抽离出来
-`ProjectPanel` 只消费命令,而不自己拼文件系统规则
当前最值得明确的一点是:旧文档里提到的项目级批量场景迁移入口已经不在这个头文件里,当前保留下来的项目维护动作只有:
- 保存项目
- 重建脚本程序集
- 切换项目 / 新建项目 / 打开项目
这才是当前源码里的真实分层。
## 测试锚点
`tests/Editor/test_action_routing.cpp` 当前直接覆盖或锚定了这组命令的关键行为:
2026-03-27 14:40:29 +08:00
2026-04-08 16:07:03 +08:00
- `ProjectCommandsReportWhenScriptAssembliesCanBeRebuilt`
- `ProjectCommandsCreateFolderMoveAssetAndOpenFolderHelper`
- `ProjectCommandsCreateFolderUsesUniqueDefaultName`
- `ProjectCommandsRenameAssetUpdatesSelectionAndPreservesFileExtension`
- `ProjectCommandsRejectInvalidMoveTargets`
- `ProjectCommandsRejectMovingFolderIntoItsDescendant`
2026-03-27 14:40:29 +08:00
## 当前限制
2026-04-08 16:07:03 +08:00
- 资源打开仍然只覆盖文件夹和场景。
- 创建材质当前写的是内置默认 JSON 骨架,没有更复杂模板选择。
- 删除、移动、重命名仍然没有事务预览或冲突解决 UI。
- 资源复制 / 粘贴 / 重新导入仍未形成命令层协议。
- 脚本重建当前只返回布尔值,不负责弹窗反馈或进度 UI主菜单层也不会消费这个返回值。
2026-03-27 14:40:29 +08:00
## 相关文档
- [Commands](../Commands.md)
2026-04-08 16:07:03 +08:00
- [MainMenuActionRouter](../../Actions/MainMenuActionRouter/MainMenuActionRouter.md)
- [ProjectPanel](../../panels/ProjectPanel/ProjectPanel.md)
2026-03-27 14:40:29 +08:00
- [ProjectActionRouter](../../Actions/ProjectActionRouter/ProjectActionRouter.md)
2026-04-08 16:07:03 +08:00
- [IProjectManager](../../Core/IProjectManager/IProjectManager.md)
2026-03-27 14:40:29 +08:00
- [ProjectManager](../../Managers/ProjectManager/ProjectManager.md)
- [SceneCommands](../SceneCommands/SceneCommands.md)
2026-04-08 16:07:03 +08:00
- [Application](../../Application/Application.md)