# ProjectPanel **命名空间**: `XCEngine::Editor` **类型**: `class` **源文件**: `editor/src/panels/ProjectPanel.h` **描述**: 项目资源浏览面板,负责把 `IProjectManager` 当前目录模型渲染成“目录树 + breadcrumb + 搜索 + 资源网格 + 上下文菜单 + 拖放”的 Project Browser。 ## 概述 `ProjectPanel` 当前是 Editor 里项目资源浏览的前端外壳,但它不是资产数据库本身。 它的职责边界比较明确: - 项目目录模型与选择状态来自 [IProjectManager](../../Core/IProjectManager/IProjectManager.md) - 资源打开、创建、删除、重命名、移动等语义落到 [ProjectCommands](../../Commands/ProjectCommands/ProjectCommands.md) - 少量通用交互协议由 [ProjectActionRouter](../../Actions/ProjectActionRouter/ProjectActionRouter.md) 提供 - 面板自身主要负责布局、即时模式 UI 状态和延迟动作收口 ## 生命周期与公开入口 - [Constructor](Constructor.md) 创建标题固定为 `"Project"` 的面板。 - [Initialize](Initialize.md) 把项目路径初始化工作委托给当前 `IProjectManager`。 - [Render](Render.md) 执行完整的项目浏览器绘制与交互驱动。 ## 面板内部状态 `ProjectPanel` 当前长期持有的主要是 UI 状态,而不是项目数据本体: | 状态 | 作用 | |------|------| | `m_searchBuffer` | 当前目录搜索关键字 | | `m_navigationWidth` | 左侧目录树宽度 | | `m_folderTreeState` | 目录树展开状态 | | `m_renameState` | 行内重命名状态 | | `m_assetDragDropState` | 本帧拖放源 / 目标状态 | | `m_deferredContextAction` | 延迟到本帧末尾执行的上下文菜单动作 | ## 初始化与每帧主流程 ### [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)` - 不做全项目搜索、类型过滤或标签过滤 ## 左侧目录树 ### 数据来源 目录树主要消费: - `GetRootFolder()` - `GetCurrentFolder()` ### 节点绘制与交互 每个目录节点当前通过 `UI::DrawTreeNode(...)` 绘制,并带有: - 当前目录高亮 - 基于 `IsCurrentTreeBranch(...)` 的默认展开 - 自定义 folder icon prefix 当前支持的主要交互包括: - 左键点击目录 -> `NavigateToFolder(folder)` - 目录作为资源拖放目标 - 空白区右键弹出项目上下文菜单 若根目录不可用,则显示 `No Assets Folder` 空状态。 ## 右侧浏览区 右侧浏览区分成两部分: - `RenderBrowserHeader(manager)` - `RenderBrowserPane(manager)` 的资源网格主体 ### Breadcrumb Header 当前通过 `UI::DrawToolbarBreadcrumbs(...)` 渲染路径,并消费: - `manager.GetPathDepth()` - `manager.GetPathName(index)` - `manager.NavigateToIndex(index)` 因此目录导航当前主要依赖目录树和 breadcrumb,而不是单独的“后退按钮”。 ### 资源网格 主体区域当前会: 1. 先基于搜索关键字计算 `visibleItems` 2. 动态估算卡片高度与列数 3. 逐项调用 `RenderAssetItem(...)` 4. 在帧末统一处理选中、打开与空白区清选 `RenderAssetItem(...)` 当前支持: - 单击选中 - 双击打开 - 右键选中并弹出上下文菜单 - 纹理预览与图标 fallback - 行内重命名 - 拖放源 / 目录拖放目标 若搜索结果为空,会显示: - `No Search Results` - `No assets match the current search` ## 上下文菜单与重命名 ### 行内重命名 当前重命名由 `m_renameState` 驱动: 1. `BeginRename(item)` 激活编辑状态 2. `DrawInlineRenameFieldAt(...)` 覆盖卡片 label 区 3. 提交时调用 `CommitRename(...)` 4. 真正的重命名落到 `Commands::RenameAsset(...)` 若用户没有实际修改显示名称,`CommitRename(...)` 会直接结束重命名,而不会重复提交文件操作。 ### 右键菜单 `DrawProjectContextMenu(...)` 当前支持: - `Create -> Folder` - `Create -> Material` - `Show in Explore` - `Open` - `Delete` - `Rename` - `Copy Path` 其中创建资源采用 deferred action: 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()` 时,面板会在下一帧反映新的目录树状态。 ## 测试锚点 `tests/Editor/test_action_routing.cpp` 当前与这条资源链路直接相关的测试包括: - `ProjectCommandsCreateFolderMoveAssetAndOpenFolderHelper` - `ProjectCommandsCreateFolderUsesUniqueDefaultName` - `ProjectCommandsRenameAssetUpdatesSelectionAndPreservesFileExtension` - `ProjectCommandsRejectInvalidMoveTargets` - `ProjectSelectionSurvivesRefreshWhenItemOrderChanges` - `ProjectCommandsRejectMovingFolderIntoItsDescendant` ## 当前限制 - 搜索当前只覆盖当前目录的名称匹配。 - 目录树展开状态当前只保存在内存里。 - 拖拽预览 tooltip 被关闭,拖放反馈比较轻量。 - 资源上下文菜单仍偏基础,没有批量操作或复杂预览。 - 面板本身不是项目保存或脚本重建入口。 ## 相关文档 - [Constructor](Constructor.md) - [Initialize](Initialize.md) - [Render](Render.md) - [panels](../panels.md) - [ProjectActionRouter](../../Actions/ProjectActionRouter/ProjectActionRouter.md) - [ProjectCommands](../../Commands/ProjectCommands/ProjectCommands.md) - [IProjectManager](../../Core/IProjectManager/IProjectManager.md) - [ProjectManager](../../Managers/ProjectManager/ProjectManager.md) - [AssetItem](../../Core/AssetItem/AssetItem.md) - [Widgets](../../UI/Widgets/Widgets.md)