chore: sync workspace state
This commit is contained in:
@@ -2,49 +2,110 @@
|
||||
|
||||
**命名空间**: `XCEngine::Editor`
|
||||
|
||||
**类型**: `class + enum class`
|
||||
**类型**: `class`
|
||||
|
||||
**源文件**: `editor/src/panels/HierarchyPanel.h`
|
||||
|
||||
**描述**: 层级面板,负责显示场景对象树、搜索、排序、拖放层级调整以及内联重命名。
|
||||
**描述**: 编辑器场景层级面板,负责绘制根实体树、处理选择与重命名事件,并把上下文菜单与拖放操作路由给 Action 层。
|
||||
|
||||
## 概述
|
||||
|
||||
`HierarchyPanel` 是当前编辑器里交互最丰富的面板之一。
|
||||
`HierarchyPanel` 是当前编辑器里最典型的“视图层面板”:它自己拥有 UI 状态,但不直接承担场景编辑命令实现。它的主要任务是把当前场景对象以树形方式呈现出来,并把用户交互转换成选择、重命名、右键菜单和拖放请求。
|
||||
|
||||
它当前承载的功能包括:
|
||||
按当前实现,这个面板主要负责:
|
||||
|
||||
- 订阅选择变化和重命名请求事件
|
||||
- 层级树渲染
|
||||
- 搜索过滤
|
||||
- 排序选项
|
||||
- 拖拽层级调整
|
||||
- 右键上下文菜单
|
||||
- 内联重命名
|
||||
- 订阅选择变化与外部重命名请求事件。
|
||||
- 读取 `ISceneManager` 提供的根实体列表。
|
||||
- 基于 `UI::TreeView` 递归绘制场景对象树。
|
||||
- 维护内联重命名状态。
|
||||
- 将选择、右键菜单、拖放等行为委托给 `Actions::HierarchyActionRouter` 一侧的 helper。
|
||||
|
||||
头文件里还定义了:
|
||||
这和商业引擎编辑器的分层很一致:Hierarchy 面板负责“看见和触发”,而不是直接“改数据和执行业务命令”。
|
||||
|
||||
- `enum class SortMode { Name, ComponentCount, TransformFirst }`
|
||||
## 当前实现行为
|
||||
|
||||
## 当前实现说明
|
||||
按 `editor/src/panels/HierarchyPanel.cpp` 的实现:
|
||||
|
||||
- `OnAttach()` 会订阅 `SelectionChangedEvent` 和 `EntityRenameRequestedEvent`。
|
||||
- 搜索过滤当前按名字子串匹配,并递归检查子节点。
|
||||
- 排序支持:
|
||||
- `Name`
|
||||
- `ComponentCount`
|
||||
- `TransformFirst`
|
||||
- 双击节点会进入重命名状态。
|
||||
- 背景点击、右键菜单和拖放逻辑主要委托给 `Actions` 层。
|
||||
- `OnAttach()` 会订阅:
|
||||
- `SelectionChangedEvent`
|
||||
- `EntityRenameRequestedEvent`
|
||||
- `OnDetach()` 会正确取消订阅并清零 handler id。
|
||||
- `Render()` 会:
|
||||
- 应用 `HierarchyInspectorPanelBackgroundColor()`。
|
||||
- 通过 `PanelWindowScope` / `PanelContentScope` 建立标准面板外壳。
|
||||
- 调用 `Actions::ObserveFocusedActionRoute(...)` 将当前焦点路由标记为 `Hierarchy`。
|
||||
- 向 `ISceneManager` 读取根实体并逐个递归渲染。
|
||||
- 在树绘制完成后处理背景点击、背景右键菜单、实体右键菜单和根级 drop target。
|
||||
|
||||
## 当前实现边界
|
||||
## 节点绘制模型
|
||||
|
||||
- 当前排序和过滤都在 UI 渲染阶段直接对实体列表做处理。
|
||||
- `TransformFirst` 排序实际上是“有 Transform 的对象优先”,而不是更复杂的 transform-aware hierarchy order。
|
||||
当前的节点绘制已经不再是旧版本那种局部自定义树,而是完全建立在 `UI::TreeView` 之上:
|
||||
|
||||
- 每个实体节点都会构造一个 `UI::TreeNodeDefinition`。
|
||||
- `selected` 状态来自 `ISelectionManager::IsSelected(...)`。
|
||||
- `leaf` 状态来自 `gameObject->GetChildCount() == 0`。
|
||||
- `persistenceKey` 使用 `gameObject->GetUUID()` 转成字符串,保证展开状态在同一面板实例中稳定。
|
||||
- `style` 使用 `UI::HierarchyTreeStyle()`。
|
||||
- `prefix` 使用 `UI::NavigationTreePrefixWidth()` 和 `UI::DrawAssetIcon(..., AssetIconKind::GameObject)` 绘制对象图标。
|
||||
|
||||
这意味着当前 Hierarchy 的视觉和交互行为已经被统一纳入 Editor UI 基础设施,而不是面板自己手工维护整套树渲染细节。
|
||||
|
||||
## 交互行为
|
||||
|
||||
当前节点交互通过 `TreeNodeCallbacks` 接入:
|
||||
|
||||
- 单击左键:调用 `Actions::HandleHierarchySelectionClick(...)`,支持结合 `Ctrl` 执行多选。
|
||||
- 右键:调用 `Actions::HandleHierarchyItemContextRequest(...)` 打开目标实体上下文菜单。
|
||||
- 双击:调用 `BeginRename(...)` 进入内联重命名。
|
||||
- 节点额外渲染阶段:调用 `Actions::BeginHierarchyEntityDrag(...)` 和 `Actions::AcceptHierarchyEntityDrop(...)` 接入拖放。
|
||||
|
||||
因此 `HierarchyPanel` 自己不直接执行“重排父子关系”之类操作,它只负责在正确的节点生命周期里提供 drop source / target 入口。
|
||||
|
||||
## 重命名模型
|
||||
|
||||
`HierarchyPanel` 当前用 `UI::InlineTextEditState<uint64_t, 256>` 保存重命名状态。
|
||||
|
||||
当前行为是:
|
||||
|
||||
- 外部若派发 `EntityRenameRequestedEvent`,面板会在 `OnRenameRequested(...)` 中找到实体并进入重命名。
|
||||
- 若用户双击树节点,也会进入重命名。
|
||||
- 编辑过程中:
|
||||
- `Enter` 提交。
|
||||
- `Escape` 取消。
|
||||
- 输入框失焦且用户左键点击其他位置时提交。
|
||||
- `CommitRename()` 最终通过 `Actions::CommitEntityRename(...)` 把改名请求发往动作层。
|
||||
|
||||
这类实现很像商业编辑器中的“inline rename controller”:面板持有输入框状态,但真正的实体改名仍然走统一命令路径。
|
||||
|
||||
## 生命周期与线程语义
|
||||
|
||||
- 该面板依赖有效的 `IEditorContext` 才能 attach、订阅事件并绘制。
|
||||
- 事件订阅和 UI 绘制都应视为编辑器主线程行为。
|
||||
- `m_treeState` 和 `m_renameState` 都属于面板实例私有状态,不会自动跨会话持久化。
|
||||
|
||||
## 设计说明
|
||||
|
||||
当前实现的一个正确方向是:Hierarchy 面板只负责“把场景对象转成 Editor UI 语义”,而不是把所有业务都塞进面板里。
|
||||
|
||||
这样做的收益是:
|
||||
|
||||
- 树视图风格、图标前缀、展开行为可以和 `ProjectPanel` 共享。
|
||||
- 选择、重命名、上下文菜单、拖放都能被 `Actions` 层集中治理。
|
||||
- 面板本身更容易继续重构,比如后续加入搜索、过滤、批量操作时,不必推翻底层树控件。
|
||||
|
||||
## 当前限制
|
||||
|
||||
- 当前没有搜索、过滤、排序或大场景虚拟化能力。
|
||||
- 展开状态只保存在 `UI::TreeViewState` 的内存态中,不会自动写入布局文件。
|
||||
- 重命名输入框是替换式绘制,不是“树节点原位嵌入子控件”的更复杂实现。
|
||||
- 目前只为每个节点提供统一的 `GameObject` 图标前缀,没有按组件类型做更细粒度图标区分。
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [panels](../panels.md)
|
||||
- [SelectionManager](../../Core/SelectionManager/SelectionManager.md)
|
||||
- [SceneManager](../../Managers/SceneManager/SceneManager.md)
|
||||
- [TreeView](../../UI/TreeView/TreeView.md)
|
||||
- [BuiltInIcons](../../UI/BuiltInIcons/BuiltInIcons.md)
|
||||
- [HierarchyActionRouter](../../Actions/HierarchyActionRouter/HierarchyActionRouter.md)
|
||||
- [EditorEvents](../../Core/EditorEvents/EditorEvents.md)
|
||||
- [ISceneManager](../../Core/ISceneManager/ISceneManager.md)
|
||||
- [ISelectionManager](../../Core/ISelectionManager/ISelectionManager.md)
|
||||
|
||||
@@ -6,36 +6,121 @@
|
||||
|
||||
**源文件**: `editor/src/panels/ProjectPanel.h`
|
||||
|
||||
**描述**: 项目面板,负责展示当前资产目录、面包屑导航、搜索、资产瓦片交互以及文件夹创建与上下文菜单。
|
||||
**描述**: 编辑器项目资源浏览面板,负责目录树、面包屑、搜索框、资源卡片和拖放交互的前端呈现。
|
||||
|
||||
## 概述
|
||||
|
||||
`ProjectPanel` 当前是 [ProjectManager](../../Managers/ProjectManager/ProjectManager.md) 的主要 UI 前端。
|
||||
`ProjectPanel` 是当前 Editor 中最接近“资源浏览器”的面板。它本身不维护项目资源数据库,而是把 `IProjectManager` 暴露的数据模型转成一个典型的双栏浏览界面:
|
||||
|
||||
它负责:
|
||||
- 左侧是目录树导航。
|
||||
- 右侧是当前目录的资源浏览区。
|
||||
- 顶部是搜索框与面包屑。
|
||||
|
||||
- 初始化项目浏览器
|
||||
- 绘制工具栏与面包屑
|
||||
- 绘制资产网格
|
||||
- 搜索过滤
|
||||
- 资产点击、打开、拖放和右键菜单
|
||||
- 空白区域上下文菜单
|
||||
- 新建文件夹弹窗
|
||||
如果和 Unity 对照,可以把它理解成一个更轻量、当前聚焦于基础浏览和拖放的 Project Browser。
|
||||
|
||||
## 当前实现说明
|
||||
## 当前实现行为
|
||||
|
||||
- `Initialize(projectPath)` 直接调用 `m_context->GetProjectManager().Initialize(projectPath)`。
|
||||
- `Render()` 中资产会按自适应列数排成网格。
|
||||
- 搜索当前按名字子串过滤。
|
||||
- 资产图标当前按 `isFolder` 区分 folder/file,再配合 action/router 决定交互。
|
||||
按 `editor/src/panels/ProjectPanel.cpp` 的实现:
|
||||
|
||||
## 当前实现边界
|
||||
- `Initialize(projectPath)` 直接委托给 `m_context->GetProjectManager().Initialize(projectPath)`。
|
||||
- `Render()` 会:
|
||||
- 建立标准 `PanelWindowScope`。
|
||||
- 把焦点动作路由标记为 `EditorActionRoute::Project`。
|
||||
- 先渲染 toolbar。
|
||||
- 再创建左右分栏内容区。
|
||||
- 使用 `UI::DrawSplitter(...)` 支持调整左侧导航栏宽度。
|
||||
- 收尾时绘制“新建文件夹”对话框。
|
||||
|
||||
- 当前搜索是前端过滤,不是索引搜索。
|
||||
- 资产预览目前还是轻量瓦片,不是完整导入数据库浏览器。
|
||||
当前导航栏宽度会被显式夹紧在一个合理范围内:
|
||||
|
||||
- 最小值来自 `ProjectNavigationMinWidth()`
|
||||
- 右侧浏览区最小宽度来自 `ProjectBrowserMinWidth()`
|
||||
|
||||
这说明当前实现已经从一开始就按“可调整工作区”的编辑器思路组织,而不是固定死尺寸。
|
||||
|
||||
## 左侧目录树
|
||||
|
||||
左侧目录树是当前 `ProjectPanel` 最典型的共享 UI 基建使用者之一:
|
||||
|
||||
- 根节点来自 `IProjectManager::GetRootFolder()`。
|
||||
- 当前目录来自 `IProjectManager::GetCurrentFolder()`。
|
||||
- 每个目录节点都通过 `UI::DrawTreeNode(...)` 绘制。
|
||||
- 节点样式使用 `UI::ProjectFolderTreeStyle()`。
|
||||
- 节点前缀使用 `UI::DrawAssetIcon(..., AssetIconKind::Folder)`。
|
||||
- `defaultOpen` 会根据 `IsCurrentTreeBranch(...)` 判断当前目录是否位于该目录分支下。
|
||||
- 展开状态保存在 `m_folderTreeState` 中。
|
||||
|
||||
用户单击树节点时,会立即调用 `manager.NavigateToFolder(folder)` 完成导航;右键则通过 `Actions::HandleProjectItemContextRequest(...)` 打开对应上下文菜单。
|
||||
|
||||
## 右侧浏览区
|
||||
|
||||
右侧浏览区分成两层:
|
||||
|
||||
- Header:显示面包屑导航,并在底部画一条 divider。
|
||||
- Body:以资源网格形式显示当前目录下通过搜索过滤后的条目。
|
||||
|
||||
资源卡片当前通过 `UI::DrawAssetTile(...)` 绘制,并根据 `item->isFolder` 区分:
|
||||
|
||||
- `Folder` 图标
|
||||
- `File` 图标
|
||||
|
||||
交互结果统一收集到 `AssetItemInteraction` 中,再在循环结束后统一处理。这种写法有一个很实用的好处:可以避免在绘制过程中直接修改当前迭代容器或导航状态,让即时模式 UI 代码更稳定。
|
||||
|
||||
## 搜索与导航
|
||||
|
||||
当前搜索实现比较明确,也需要在文档里写清楚:
|
||||
|
||||
- 搜索框位于 toolbar 右侧。
|
||||
- 搜索只对 `manager.GetCurrentItems()` 返回的当前目录条目生效。
|
||||
- 匹配逻辑是大小写不敏感的子串匹配。
|
||||
- 当前没有全文索引、标签过滤或类型过滤。
|
||||
|
||||
面包屑则通过 `UI::DrawToolbarBreadcrumbs(...)` 实现,点击非当前段会调用 `manager.NavigateToIndex(index)`。
|
||||
|
||||
## 拖放与上下文菜单
|
||||
|
||||
当前 `ProjectPanel` 并不自己实现拖放协议和命令执行,而是按职责分层调用其他模块:
|
||||
|
||||
- `Actions::BeginProjectAssetDrag(item, iconKind)` 负责发起拖拽源。
|
||||
- `Actions::AcceptProjectAssetDropPayload(item)` 负责检测是否有资源被拖到当前目标上。
|
||||
- 若确实发生移动,则调用 `Commands::MoveAssetToFolder(...)`。
|
||||
- 右键菜单与空白区域菜单由 `Actions::*ContextPopup(...)` 系列 helper 负责绘制。
|
||||
|
||||
这种组织方式和 `HierarchyPanel` 一样,遵循的是“面板负责展示与采样,动作层 / 命令层负责实际编辑行为”的编辑器架构。
|
||||
|
||||
## 生命周期与线程语义
|
||||
|
||||
- 面板本身持有的是 UI 状态:搜索文本、导航栏宽度、目录树展开状态、弹窗状态。
|
||||
- 真实项目数据由 `IProjectManager` 持有。
|
||||
- 整套逻辑应视为编辑器 UI 主线程代码。
|
||||
|
||||
## 设计说明
|
||||
|
||||
当前 `ProjectPanel` 的设计方向是合理的,因为它先把“资源浏览器”拆成几个稳定的基础体验单元:
|
||||
|
||||
- 分栏布局
|
||||
- 目录树
|
||||
- 面包屑
|
||||
- 资源网格
|
||||
- 拖放与上下文菜单
|
||||
|
||||
这样做的好处是,后续无论是加缩略图、资源导入器状态、筛选器还是收藏夹,都可以在现有骨架上演进,而不是推倒重来。
|
||||
|
||||
## 当前限制
|
||||
|
||||
- 搜索只在当前目录里做前端子串过滤,不是全项目索引搜索。
|
||||
- 当前没有资源缩略图生成或异步预览系统,图标仍以 `BuiltInIcons` 和简单卡片为主。
|
||||
- 文件树展开状态只保存在内存中,不会自动跨重启恢复。
|
||||
- 拖放移动是直接命令式处理,没有更复杂的批处理或事务预览。
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [panels](../panels.md)
|
||||
- [IProjectManager](../../Core/IProjectManager/IProjectManager.md)
|
||||
- [ProjectManager](../../Managers/ProjectManager/ProjectManager.md)
|
||||
- [AssetItem](../../Core/AssetItem/AssetItem.md)
|
||||
- [ProjectActionRouter](../../Actions/ProjectActionRouter/ProjectActionRouter.md)
|
||||
- [ProjectCommands](../../Commands/ProjectCommands/ProjectCommands.md)
|
||||
- [TreeView](../../UI/TreeView/TreeView.md)
|
||||
- [BuiltInIcons](../../UI/BuiltInIcons/BuiltInIcons.md)
|
||||
- [SplitterChrome](../../UI/SplitterChrome/SplitterChrome.md)
|
||||
|
||||
Reference in New Issue
Block a user