# ProjectCommands **命名空间**: `XCEngine::Editor::Commands` **类型**: `header-helper` **源文件**: `editor/src/Commands/ProjectCommands.h` **描述**: Project 工作流命令集合,封装项目切换与保存、资源浏览与文件操作,以及脚本程序集重建等高层动作。 ## 概述 `ProjectCommands` 当前已经不只是“打开资源 + 建目录 + 删除/移动”的薄命令头。 按 `ProjectCommands.h` 的真实实现,它覆盖四组链路: - 资源浏览与编辑命令 - 项目文档保存命令 - 脚本程序集重建 - 项目切换与项目目录初始化 和 [ProjectPanel](../../panels/ProjectPanel/ProjectPanel.md) 的关系也很清楚: - 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` 一定会出现新条目,因为脚本程序集产物主要位于 `/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(...)` ## 设计说明 当前 `ProjectCommands` 的设计方向很清楚: - 把资源管理规则集中化 - 把项目文档保存链路集中化 - 把项目维护类动作从菜单 / 面板层抽离出来 - 让 `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)