chore: sync workspace state

This commit is contained in:
2026-03-29 01:36:53 +08:00
parent eb5de3e3d4
commit e5cb79f3ce
4935 changed files with 35593 additions and 360696 deletions

View File

@@ -4,51 +4,86 @@
**类型**: `app-module`
**描述**: 编辑器应用层 API 文档入口,镜像 `editor/src`目录结构,覆盖编辑器启动、上下文、面板工作区、项目浏览、场景编辑与 UI 基础设施。
**描述**: XCEngine 独立编辑器应用层 API 文档入口,对应 `editor/src/**`模块结构,覆盖应用启动、上下文、面板、布局、命令、动作路由与 UI 基础设施。
## 概述
这一组文档对应的不是 `engine/include/XCEngine` 的 public engine headers,而是独立编辑器应用 `editor/src/**`
这一组文档对应的不是 `engine/include/XCEngine/**` 那类“引擎公共运行时头文件”,而是编辑器应用本身的源码模块。换句话说,这里的 API 更接近“工具层架构说明”和“编辑器内部可复用接口”,而不是给游戏运行时代码直接依赖的稳定 SDK
因此这里需要先建立一个正确心智模型
当前 Editor 的主链路可以概括为
- `XCEngine` 引擎模块负责运行时系统。
- `Editor` 模块负责围绕这些运行时系统搭建编辑器应用
- 它更接近“应用层/工具层 API”而不是给游戏代码直接依赖的稳定引擎 ABI。
1. [Application](Application/Application.md)
启动 Win32 窗口、D3D12 窗口渲染器、ImGui 会话与编辑器上下文
2. [Core](Core/Core.md)
组织事件总线、选择系统、撤销系统、场景与项目管理接口。
3. [Layers](Layers/Layers.md)
`EditorLayer` 承担编辑器工作区生命周期。
4. [Layout](Layout/Layout.md)
构建 dockspace、默认布局与工作区停靠关系。
5. [panels](panels/panels.md)
承载 Scene / Game / Hierarchy / Inspector / Console / Project 等主要工作面板。
6. [UI](UI/UI.md)
承载 Dear ImGui 上层的主题 token、chrome、树视图、属性布局和通用 widget。
当前编辑器的主链路大致是:
## 架构定位
1. [Application](Application/Application.md) 启动 Win32 窗口、D3D12 窗口渲染器和 ImGui 会话
2. [Core::EditorContext](Core/EditorContext/EditorContext.md) 组装事件总线、场景管理、项目管理、选择管理和撤销系统。
3. [Layers::EditorLayer](Layers/EditorLayer/EditorLayer.md) 承载编辑器工作区生命周期。
4. [Core::EditorWorkspace](Core/EditorWorkspace/EditorWorkspace.md) 组织菜单、层级、场景视图、GameView、Inspector、Console 和 Project 等面板。
从架构视角看,`Editor` 模块解决的是“如何把引擎运行时能力组织成一个可编辑、可观察、可操作的工具应用”
这和商业游戏引擎的常见分层非常一致:
- 引擎运行时负责场景、组件、渲染、资源和脚本能力。
- 编辑器应用负责把这些能力包装成面板、命令、菜单、工作区与交互流。
因此阅读这套文档时最重要的前置认知是Editor 层的很多接口是为工具体验服务的,它们允许比运行时 API 更强的耦合、更明确的 UI 约束和更直接的产品导向。
## 当前重构重点
结合当前 `editor/src` 的实现Editor 文档当前有几个结构上非常重要的重构点:
- `UI.h` 现在明确作为 umbrella header 处理,其职责是聚合常用 UI helper而不是声明新的运行时类型。
- `TreeView``PropertyLayout``BuiltInIcons``DockTabBarChrome` 等新基础设施已经成为多个面板共享的 UI 基建。
- `HierarchyPanel``ProjectPanel` 的实现已经明显转向“面板只负责呈现与路由,动作层 / 命令层负责执行业务”的分层。
这意味着 Editor 文档不能只做目录映射,还必须解释这些共享基础设施为什么存在、它们如何把编辑器代码从面板私有技巧提升为稳定的产品层模式。
## 聚合与辅助文件
并不是 `editor/src/**` 下每个文件都值得成为单独 API 页。当前有几类文件更适合在模块页说明:
- `editor/src/UI/UI.h`
这是 UI 子模块的 umbrella header文档并入 [UI](UI/UI.md)。
- `editor/src/EditorResources.h`
当前只定义了 `IDI_APP_ICON 101`,用于 `EditorApp.rc` 的资源 id 绑定,更适合作为 Editor 应用资源入口说明,而不是独立类型页。
- `editor/src/EditorApp.rc``editor/src/main.cpp`
属于应用启动与资源清单支撑文件,不是面向上层复用的编辑器 API。
这种处理方式符合当前文档规范:只有真正承担模块职责或可复用契约的文件才进入 canonical API 树;纯资源声明和应用入口支撑文件优先并入模块说明。
## 当前实现边界
- 当前编辑器主要是 Windows + D3D12 + ImGui 路径
-代码整体应用层源码,不像 engine public headers 那样已经完全按稳定 SDK 方式整理
- 当前文档页会优先标注 `源文件`,而不是 `头文件`,以反映它们来自 `editor/src/**`
- 当前自动审计脚本仍以 `engine/include/XCEngine` 为主,因此 `Editor` 这组页主要靠链接完整性和人工结构约束维护。
- 当前编辑器主路径仍然以 Windows + D3D12 + Dear ImGui 为核心
-代码整体属于应用层源码,不应误解为已经整理成稳定插件 SDK
- 自动审计脚本目前主要覆盖 `engine/include/XCEngine/**` 的 public headersEditor 这组文档更依赖目录并行约束、链接校验和人工核对源码行为
## 目录
- [Application](Application/Application.md) - 顶层编辑器应用入口。
- [Theme](Theme/Theme.md) - 顶层主题入口
- [Core](Core/Core.md) - 上下文、事件、撤销、选择与基础数据结构
- [Managers](Managers/Managers.md) - 项目与场景管理实现。
- [panels](panels/panels.md) - 编辑器面板基础设施
- [Actions](Actions/Actions.md) - 菜单、快捷键、按钮动作绑定与路由
- [Commands](Commands/Commands.md) - 面向场景与项目操作的命令封装
- [ComponentEditors](ComponentEditors/ComponentEditors.md) - 组件编辑器注册与实现。
- [Core](Core/Core.md) - 上下文、事件、撤销、选择与基础接口
- [Layers](Layers/Layers.md) - 编辑器 layer 封装。
- [Platform](Platform/Platform.md) - Win32 窗口宿主与 D3D12 窗口渲染器。
- [UI](UI/UI.md) - ImGui 会话与编辑器 UI 基础设施。
- [Actions](Actions/Actions.md) - 菜单/按钮/快捷键动作绑定与路由层。
- [Commands](Commands/Commands.md) - 面向场景与项目操作的高层命令封装。
- [ComponentEditors](ComponentEditors/ComponentEditors.md) - 组件属性编辑器注册与实现层。
- [Layout](Layout/Layout.md) - Dock 布局控制。
- [Managers](Managers/Managers.md) - 项目和场景管理实现。
- [panels](panels/panels.md) - 主要工作面板。
- [Platform](Platform/Platform.md) - Win32 宿主与 D3D12 窗口渲染路径。
- [Theme](Theme/Theme.md) - 顶层主题入口。
- [UI](UI/UI.md) - Editor UI 基础设施。
- [Utils](Utils/Utils.md) - 场景编辑与撤销相关辅助函数。
## 相关文档
- [XCEngine 根目录](../XCEngine.md)
- [XCEngine](../XCEngine.md)
- [Scene](../Scene/Scene.md)
- [Components](../Components/Components.md)
- [Rendering](../Rendering/Rendering.md)

View File

@@ -0,0 +1,107 @@
# BuiltInIcons
**命名空间**: `XCEngine::Editor::UI`
**类型**: `header-helper + enum class`
**源文件**: `editor/src/UI/BuiltInIcons.h`
**描述**: 为编辑器的资源树、资源网格和层级树提供内置图标枚举、初始化入口与统一绘制函数。
## 概述
`BuiltInIcons.h` 解决的不是“如何显示一张纹理”这个底层问题,而是“编辑器里哪些地方应该用统一图标语言表达文件夹、文件和场景对象”这个产品层问题。
当前源码里,这一层主要服务于三个界面:
- `HierarchyPanel` 通过 `GameObject` 图标给场景树节点补前缀。
- `ProjectPanel` 通过 `Folder` / `File` 图标绘制左侧目录树和右侧资源卡片。
- `ProjectActionRouter` 在拖拽预览等交互里复用同一套图标语义。
这和商业级编辑器常见的做法一致:业务面板不直接持有纹理资源,不自己决定某一类对象该显示什么图标,而是把“对象类别 -> 图标表现”的决定集中到一层共享 helper 中。这样做的好处是统一、可替换,而且不会让每个面板都自己重复写上传纹理、绘制 fallback 图形、适配尺寸的逻辑。
## 公开 API
| 成员 | 说明 |
|------|------|
| `enum class AssetIconKind { Folder, File, GameObject }` | 定义当前编辑器可识别的内置图标类别。 |
| `InitializeBuiltInIcons(ImGuiBackendBridge&, ID3D12Device*, ID3D12CommandQueue*)` | 初始化图标系统,上传贴图并分配 ImGui 可见的 SRV 描述符。 |
| `ShutdownBuiltInIcons()` | 释放内置图标占用的 GPU 资源与描述符。 |
| `DrawAssetIcon(ImDrawList*, const ImVec2& min, const ImVec2& max, AssetIconKind)` | 在给定矩形内绘制指定类别的图标。 |
## 生命周期
- 这一层本质上是一个进程级的全局状态,当前实现通过匿名命名空间里的 `g_icons` 保存后端指针和已上传纹理。
- 正确顺序是先初始化 `ImGuiBackendBridge`,再调用 `InitializeBuiltInIcons(...)`
- 关闭时应先停止使用这些图标,再调用 `ShutdownBuiltInIcons()`,最后销毁 ImGui 后端。
- 当前 `Application::InitializeImGui()``Application::Shutdown()` 已经按照这个顺序接线。
## 当前实现行为
`editor/src/UI/BuiltInIcons.cpp` 的实现,当前版本有几个值得明确写进文档的事实:
- 只有 `Folder``GameObject` 两类图标会尝试从磁盘加载 PNG。
- 图标路径不是从项目目录查找,而是从可执行文件目录推导到 `../../resources/Icons/folder_icon.png``../../resources/Icons/gameobject_icon.png`
- 贴图上传路径目前只支持 D3D12上传时会创建默认堆纹理、上传堆 buffer、一次性命令列表并在结束后主动等待命令队列空闲。
- `File` 图标当前不是贴图资源,而是运行时用 `ImDrawList` 直接画出的简化文件形状。
- 如果 `Folder` / `GameObject` 的 PNG 加载失败,系统不会抛出错误或写日志;它会静默退回到程序化绘制的 fallback 图标。
这意味着它更接近“编辑器内部视觉资源辅助层”,而不是一个对外稳定暴露的通用图标资源系统。
## 设计说明
如果把它和 Unity 的使用体验类比,可以把这层理解成“轻量版的内建编辑器图标入口”。它的目标不是提供一个完整的图标数据库,而是确保最核心的编辑器对象类型在不同面板里看起来一致。
这样设计的收益是:
- 面板代码只关心“我要画文件夹图标”,而不关心纹理从哪里来。
- 资源加载失败时仍然有 fallback不会导致整个面板失去可用性。
- 以后如果要统一替换图标风格,只需要改这一层,而不是同时改 `HierarchyPanel``ProjectPanel` 和拖拽预览逻辑。
代价也很明确:
- 当前实现和 D3D12 / ImGui 后端强绑定,还不是渲染后端无关的接口。
- 图标集合很小,只覆盖当前编辑器刚需。
- 路径解析和加载失败处理还比较工程化,不是面向内容制作流程的资源系统。
## 线程语义
- 这套 API 应被视为仅限编辑器 UI / 渲染线程使用。
- 初始化和销毁会触发 GPU 资源分配、命令提交与队列等待,不适合在任意后台线程上调用。
- `DrawAssetIcon(...)` 依赖当前帧的 `ImDrawList` 和 ImGui 绘制上下文,也只能在 ImGui 绘制阶段调用。
## 典型用法
```cpp
// 初始化阶段
m_imguiBackend.Initialize(...);
UI::InitializeBuiltInIcons(
m_imguiBackend,
m_windowRenderer.GetDevice(),
m_windowRenderer.GetCommandQueue());
// 面板绘制阶段
UI::DrawAssetIcon(
ImGui::GetWindowDrawList(),
iconMin,
iconMax,
UI::AssetIconKind::Folder);
// 关闭阶段
UI::ShutdownBuiltInIcons();
```
## 当前限制
- 当前只支持 D3D12 上传路径,没有 OpenGL / Vulkan / 软件后备实现。
- 图标种类固定为 `Folder``File``GameObject` 三类。
- 没有热重载、主题切换或 DPI 变体资源管理。
- 加载失败时以静默 fallback 为主,没有诊断级错误反馈。
## 相关文档
- [UI](../UI.md)
- [StyleTokens](../StyleTokens/StyleTokens.md)
- [Application](../../Application/Application.md)
- [HierarchyPanel](../../panels/HierarchyPanel/HierarchyPanel.md)
- [ProjectPanel](../../panels/ProjectPanel/ProjectPanel.md)

View File

@@ -0,0 +1,73 @@
# DividerChrome
**命名空间**: `XCEngine::Editor::UI`
**类型**: `header-helper`
**源文件**: `editor/src/UI/DividerChrome.h`
**描述**: 提供编辑器面板分隔线的轻量绘制函数,用统一的 `StyleTokens` 控制颜色与线宽。
## 概述
`DividerChrome.h` 很小,但它承担的是“视觉一致性”而不是“复杂交互”。它把横向分隔线、纵向分隔线和当前窗口底边线抽成统一 helper避免各个面板自己散落写 `AddLine(...)` 与硬编码颜色。
在商业编辑器里,这类 helper 很常见,因为它们能把“只是画一条线”这件小事也纳入统一设计系统:线宽、颜色、出现位置都不再是面板作者自己临时决定,而是由共享 token 决定。
## 公开 API
| 成员 | 说明 |
|------|------|
| `DrawHorizontalDivider(...)` | 在任意 `ImDrawList` 上绘制一条横向分隔线。 |
| `DrawVerticalDivider(...)` | 在任意 `ImDrawList` 上绘制一条纵向分隔线。 |
| `DrawCurrentWindowBottomDivider(...)` | 读取当前 ImGui 窗口的矩形并在底边绘制横线。 |
## 当前实现行为
`editor/src/UI/DividerChrome.h` 的实现:
- 默认颜色来自 `PanelDividerColor()`
- 默认厚度来自 `PanelDividerThickness()`
- 如果传入的 `drawList` 为空,或绘制范围是退化区间,函数会直接返回。
- `DrawCurrentWindowBottomDivider(...)` 不会创建额外布局空间;它只是读取当前窗口的 `Pos``Size` 后直接在底边绘线。
这意味着它是纯绘制 helper不管理布局、不创建交互区域也不记录任何状态。
## 设计说明
把 divider 单独抽出来有两个直接好处:
- `ProjectPanel``PanelChrome`、自定义标签栏都可以复用同一套边界视觉语言。
- 当设计风格变化时,只要改 token而不是到处找 `AddLine(...)` 的裸调用。
可以把它理解成商业引擎编辑器里“panel chrome”的最小组成部分。它不像 splitter 那样负责拖拽,也不像 toolbar scope 那样负责结构;它只负责把层次边界画正确。
## 使用建议
- 如果你已经有明确的屏幕坐标,就直接调用 `DrawHorizontalDivider(...)``DrawVerticalDivider(...)`
- 如果你只是想给当前 child/window 补一条底边线,优先用 `DrawCurrentWindowBottomDivider(...)`
- 这类 helper 适合放在面板收尾阶段调用,而不是提前调用后再继续写会改变窗口高度的控件。
## 典型用法
```cpp
ImDrawList* drawList = ImGui::GetWindowDrawList();
const ImVec2 min = ImGui::GetWindowPos();
const ImVec2 max(min.x + ImGui::GetWindowSize().x, min.y + ImGui::GetWindowSize().y);
UI::DrawHorizontalDivider(drawList, min.x, max.x, max.y - 0.5f);
```
## 当前限制
- 不负责布局留白,调用者必须自己决定线的位置。
- 没有 hover / active 等交互状态,它不是 splitter。
- 只是一层对 `ImDrawList::AddLine(...)` 的统一封装,不提供更复杂的边框系统。
## 相关文档
- [UI](../UI.md)
- [StyleTokens](../StyleTokens/StyleTokens.md)
- [Core](../Core/Core.md)
- [PanelChrome](../PanelChrome/PanelChrome.md)
- [ProjectPanel](../../panels/ProjectPanel/ProjectPanel.md)

View File

@@ -0,0 +1,103 @@
# DockTabBarChrome
**命名空间**: `XCEngine::Editor::UI`
**类型**: `header-helper`
**源文件**: `editor/src/UI/DockTabBarChrome.h`
**描述**: 基于 ImGui Docking 内部节点实现自定义停靠标签栏外观、选中逻辑、拖拽排序与拖出浮动行为。
## 概述
`DockTabBarChrome.h` 是当前 Editor UI 里技术耦合度最高的一层 helper 之一。它的职责不是创建 dockspace而是在 ImGui 已经提供 docking 能力的前提下,把默认标签栏替换成一套更像商业编辑器的自定义标签栏。
当前这套方案由两部分组成:
- `ConfigureDockTabBarChrome(...)` 递归配置 dock node关闭原生 tab bar并确保叶子节点处于可绘制的稳定状态。
- `DrawDockedWindowTabStrip()` 在具体面板窗口内部补画自定义标签条。
从调用关系上看:
- `DockLayoutController::RenderDockspace()` 负责每帧配置 dockspace 节点。
- `PanelChrome::PanelWindowScope``ImGui::Begin(...)` 成功后调用 `DrawDockedWindowTabStrip()`
也就是说,这一层本质上是“布局层 + 面板 chrome 层”的协作产物,而不是某一个面板自己的私有实现。
## 建议视为公开入口的函数
虽然头文件里存在很多 inline helper但从设计意图上真正应该被其他模块直接依赖的入口主要只有下面三个
| 成员 | 说明 |
|------|------|
| `ConfigureDockTabBarChrome(ImGuiDockNode* node)` | 对指定 dock node 递归应用自定义 tab bar 策略。 |
| `ConfigureDockTabBarChrome(ImGuiID dockspaceId)` | 通过 dockspace id 找到 root node 并应用同样策略。 |
| `DrawDockedWindowTabStrip()` | 在当前面板窗口顶部绘制自定义标签条。 |
其他诸如 `DockTabOrderCache()``ReorderDockTab(...)``BeginCustomDockTabUndock(...)` 之类的函数,虽然在头文件可见,但更适合作为当前实现细节理解,而不是稳定的上层调用契约。
## 当前实现行为
`editor/src/UI/DockTabBarChrome.h` 的实现,当前版本具备以下行为:
- 对叶子 `ImGuiDockNode` 会强制设置 `ImGuiDockNodeFlags_NoTabBar``NoWindowMenuButton``NoCloseButton`,隐藏 ImGui 原生标签栏。
- 通过静态 `std::unordered_map<ImGuiID, std::vector<ImGuiID>>` 维护每个 dock node 的 tab 顺序缓存。
- 如果当前叶子节点没有显式选中页签,系统会把第一个窗口设为选中页签。
- 自定义标签条支持:
- 单击切换选中页签。
- 左键拖拽改变页签顺序。
- 满足阈值时将页签从当前 dock 节点拖出,进入 ImGui 原生的 undock 流程。
- 标签颜色、背景色与分隔线使用 `StyleTokens` 和 ImGui dock style 的组合结果。
- 标签宽度当前按文本宽度加固定水平 padding 计算,没有做更复杂的压缩或滚动策略。
这套实现明显不是简单换皮,而是接管了 tab strip 的一部分交互语义。
## 设计说明
为什么要做这一层,而不是直接接受 ImGui 默认标签栏?
- 商业编辑器通常会把工作区标签视觉做得更克制、更稳定,以便和工具栏、面板边框形成统一外观。
- 一旦需要控制页签排序、激活行为、拖出阈值、底部分隔线这些细节,直接用默认样式往往不够。
- 把这部分逻辑集中在 `DockTabBarChrome.h` 中,能避免 `Hierarchy``Scene``Console` 这类面板各自处理 dock 标签外观。
如果和 Unity 的使用体验类比,这层更像“编辑器工作区标签栏的定制实现”,而不是某个单独窗口组件。
## 前置条件与线程语义
- 只能在启用了 ImGui Docking 的编辑器 UI 帧里使用。
- `ConfigureDockTabBarChrome(...)` 应在 dockspace 建立后、面板开始绘制前调用;当前由 `DockLayoutController` 统一负责。
- `DrawDockedWindowTabStrip()` 需要当前窗口已经 `Begin(...)` 成功且窗口处于 docked 状态;当前由 `PanelWindowScope` 自动调用。
- 这一层依赖 `imgui_internal.h` 暴露的内部结构,应视为 UI 主线程专用逻辑,不具备线程安全保证。
## 典型用法
```cpp
const ImGuiID dockspaceId = ImGui::GetID("MainDockspace.Root");
UI::ConfigureDockTabBarChrome(dockspaceId);
{
UI::DockHostStyleScope dockHostStyle;
ImGui::DockSpace(dockspaceId, ImVec2(0.0f, 0.0f), dockFlags);
}
```
面板窗口不需要自己手写标签条调用,使用 `PanelWindowScope` 时会自动进入:
```cpp
UI::PanelWindowScope panel("Hierarchy");
```
## 当前限制
- 强依赖 `imgui_internal.h``ImGuiWindow``ImGuiDockNode` 等内部实现细节,升级 ImGui 时需要重点回归。
- 当前没有 close button、window menu button也没有更复杂的 tab overflow 处理。
- tab 顺序缓存是进程内静态状态,不是跨会话布局资产。
- 这是 Editor 专用 UI 基建,不适合直接当作通用运行时 UI API 使用。
## 相关文档
- [UI](../UI.md)
- [StyleTokens](../StyleTokens/StyleTokens.md)
- [PanelChrome](../PanelChrome/PanelChrome.md)
- [DockHostStyle](../DockHostStyle/DockHostStyle.md)
- [DockLayoutController](../../Layout/DockLayoutController/DockLayoutController.md)

View File

@@ -0,0 +1,105 @@
# PropertyLayout
**命名空间**: `XCEngine::Editor::UI`
**类型**: `header-helper + struct`
**源文件**: `editor/src/UI/PropertyLayout.h`
**描述**: 为 Inspector 属性行提供统一的标签列 / 控件列几何计算,并把具体控件绘制委托给回调。
## 概述
`PropertyLayout.h` 是当前 Inspector 体验能否“像一个成熟编辑器”而不是“像一堆临时摆放的 ImGui 控件”的关键基础设施。
它不直接定义浮点框、复选框或向量输入框,而是先回答一个更底层的问题:
- 标签列从哪里开始?
- 控件列从哪里开始?
- 当前行有多高?
- 控件应该拿到多宽的可用空间?
把这层几何计算独立出来以后,`ScalarControls``VectorControls``PropertyGrid` 这些上层 helper 就可以共享同一套 Inspector 排版规则,而不需要每种控件都重复手算列宽和光标位置。
## 公开 API
| 成员 | 说明 |
|------|------|
| `PropertyLayoutSpec` | 描述标签缩进、控件列起点、标签和控件间距、控件右侧留白。 |
| `PropertyLayoutMetrics` | 保存一行属性被计算出的实际几何结果。 |
| `MakePropertyLayout()` | 生成使用默认 token 的 `PropertyLayoutSpec`。 |
| `PushPropertyLayoutStyles()` | 推入当前属性行需要的样式变量。 |
| `GetPropertyControlWidth(...)` | 根据布局结果返回控件区域宽度。 |
| `SetNextPropertyControlWidth(...)` | 用布局结果直接设置下一个控件的宽度。 |
| `AlignPropertyControlToRight(...)` | 让较窄控件在属性列中右对齐。 |
| `DrawPropertyRow(...)` | 绘制一整行属性,并把具体控件绘制委托给回调。 |
## 当前实现行为
`editor/src/UI/PropertyLayout.h` 的实现:
- `PropertyLayoutSpec` 的默认值直接来自 `StyleTokens.h`,例如 `InspectorPropertyControlColumnStart()``InspectorPropertyLabelInset()`
- `PushPropertyLayoutStyles()` 当前只推入 `ImGuiStyleVar_FramePadding`,并不负责整套 Inspector 样式。
- `DrawPropertyRow(...)` 会:
-`label``PushID` 的依据。
- 读取当前内容区宽度并计算一整行的 label / control 几何。
- 直接用 `ImDrawList::AddText(...)` 绘制标签文本,而不是创建一个独立的 ImGui label item。
- 把光标移动到控件列起点,再调用外部回调绘制真正的控件。
- 最后用 `Dummy(...)` 吃掉这一行占用的高度,确保后续布局连续。
这说明它是一层“布局执行器”,不是单纯的常量集合。
## 设计说明
商业引擎编辑器通常会把 Inspector 的“字段布局规则”和“字段编辑控件”拆开,这一点和 Unity Inspector 的使用体验很像:
- 左边是稳定的属性标签列。
- 右边是不同类型字段共享的一列编辑区域。
- 上层组件编辑器只描述“这里是一条 Position / Rotation / Scale”而不是自己计算每个字段的横向坐标。
这样拆分的收益非常直接:
- 所有组件编辑器天然保持一致的列对齐。
- 以后如果想统一调整 Inspector 视觉密度,只需要修改 token 或布局层。
- `PropertyGrid` 可以专注在“属性语义 + 撤销接线”,而不必再重复几何计算。
## 使用模式
更推荐的使用方式不是直接手算坐标,而是把控件包进 `DrawPropertyRow(...)` 的回调:
```cpp
const UI::PropertyLayoutSpec layout = UI::MakePropertyLayout();
UI::DrawPropertyRow("Mass", layout, [&](const UI::PropertyLayoutMetrics& metrics) {
UI::SetNextPropertyControlWidth(metrics);
return ImGui::DragFloat("##value", &mass, 0.1f, 0.0f, 1000.0f, "%.2f");
});
```
如果只是常见属性类型,实际工程里更常见的入口是:
- `ScalarControls.h`
- `VectorControls.h`
- `PropertyGrid.h`
它们已经把 `PropertyLayout` 作为底层依赖封装好了。
## 线程语义
- 这一层完全依赖当前帧的 ImGui 上下文和当前窗口布局状态,只能在 UI 线程、ImGui 绘制阶段调用。
- 它不维护后台状态,也不适合离线计算布局。
## 当前限制
- `PushID(label)` 意味着同一作用域里若有重复标签,调用方应额外 `PushID` 以避免冲突。
- 标签是直接画到 draw list 上的,因此不是可交互的独立 ImGui item。
- 当前布局仍然是固定列起点模型,不是响应式表单系统。
- `PushPropertyLayoutStyles()` 目前只做最小样式压栈,离完整的属性主题系统还有距离。
## 相关文档
- [UI](../UI.md)
- [StyleTokens](../StyleTokens/StyleTokens.md)
- [ScalarControls](../ScalarControls/ScalarControls.md)
- [VectorControls](../VectorControls/VectorControls.md)
- [PropertyGrid](../PropertyGrid/PropertyGrid.md)

View File

@@ -0,0 +1,88 @@
# SplitterChrome
**命名空间**: `XCEngine::Editor::UI`
**类型**: `header-helper + enum class + struct`
**源文件**: `editor/src/UI/SplitterChrome.h`
**描述**: 为编辑器面板提供统一的 splitter 主题颜色与拖拽分隔条绘制 / 交互结果。
## 概述
`SplitterChrome.h` 处理的是“两个区域之间如何被拖拽改变尺寸”这个问题。和 `DividerChrome` 不同,它不只是画一条视觉边界线,而是同时承担:
- 交互命中区域创建。
- hover / active 状态计算。
- 鼠标光标切换。
- 拖拽位移返回。
当前源码中,最直接的使用者是 `ProjectPanel`:左侧目录树和右侧资源浏览区之间的宽度调节完全依赖这层 helper。
## 公开 API
| 成员 | 说明 |
|------|------|
| `enum class SplitterAxis { Vertical, Horizontal }` | 定义 splitter 的主轴方向。 |
| `struct SplitterResult` | 返回当前 splitter 的 hover、active 与拖拽增量。 |
| `ApplySplitterThemeColors(ImVec4* colors)` | 用当前 token 覆盖 ImGui separator / resize grip 相关颜色。 |
| `DrawSplitter(...)` | 绘制 splitter 并返回本帧交互结果。 |
## 当前实现行为
`editor/src/UI/SplitterChrome.h` 的实现:
- `DrawSplitter(...)` 使用 `ImGui::InvisibleButton(...)` 创建命中区域。
- 命中区域厚度默认来自 `PanelSplitterHitThickness()`,而真正画出来的线宽来自 `PanelSplitterVisibleThickness()`
- 当 splitter 处于 active 状态时:
- 纵向 splitter 返回 `MouseDelta.x`
- 横向 splitter 返回 `MouseDelta.y`
- hover 或 active 时会把鼠标指针切到 `ResizeEW``ResizeNS`
- 它只返回“这一帧用户拖了多少”,并不直接修改任何布局变量;调用者必须自己把 `delta` 累加到宽度 / 高度并做 clamp。
因此,这层是“交互采样 + 视觉呈现”,不是完整的布局管理器。
## 设计说明
把 splitter 从具体面板里抽出来是非常典型的商业编辑器实践:
- 面板只关心自己的布局状态,例如 `m_navigationWidth`
- splitter helper 只关心拖拽命中、光标和线条绘制。
- 主题层通过 `StyleTokens` 统一控制 splitter 粗细、颜色和命中区宽度。
这样 `ProjectPanel`、后续的多列 Inspector 或资源导入面板都能使用相同的分隔条行为,而不会出现每个面板“拖动手感都不一样”的问题。
## 典型用法
```cpp
const UI::SplitterResult splitter =
UI::DrawSplitter("##ProjectPaneSplitter", UI::SplitterAxis::Vertical, totalHeight);
if (splitter.active) {
m_navigationWidth += splitter.delta;
}
```
调用者通常还应该把结果夹紧到合理区间,例如:
- 最小导航宽度
- 最小内容区宽度
## 线程语义
- 只能在 ImGui UI 线程使用。
- 每帧结果是即时的,不会自动跨帧缓存。
- `ApplySplitterThemeColors(...)` 修改的是调用方传入的颜色数组,通常应在同一帧、同一上下文中使用。
## 当前限制
- 当前只提供基础 hover / active 与位移返回,不提供吸附、双击重置、动画过渡等高级行为。
- 颜色仍然是静态 token不支持运行时主题切换动画。
- 由调用者自己负责宽度约束和布局重算,没有更高层的布局容器抽象。
## 相关文档
- [UI](../UI.md)
- [StyleTokens](../StyleTokens/StyleTokens.md)
- [DividerChrome](../DividerChrome/DividerChrome.md)
- [ProjectPanel](../../panels/ProjectPanel/ProjectPanel.md)

View File

@@ -2,45 +2,90 @@
**命名空间**: `XCEngine::Editor::UI`
**类型**: `header-helper`
**类型**: `header-helper + struct`
**源文件**: `editor/src/UI/StyleTokens.h`
**描述**: 统一定义编辑器 UI 使用的颜色、尺寸、间距和布局 token。
**描述**: 集中定义 Editor UI 的颜色、尺寸、间距与树视图样式,是当前编辑器设计系统的单一 token
## 概述
`StyleTokens.h` 是当前 UI 的设计 token 中心。
它集中提供了大量 inline token helper例如
`StyleTokens.h` 是当前 Editor UI 的设计语言中心。它把原本最容易散落在各个面板、各类 helper 和临时控件中的“魔法数字”集中成一组具名 token让代码能够表达设计意图而不是只暴露数值本身。
- Dock 标签颜色
- 工具栏高度与 padding
- 资产网格尺寸
- Inspector 标签列宽
- 弹窗按钮尺寸
- 向量控件按钮颜色
- Console 状态颜色
例如:
- `ProjectNavigationDefaultWidth()` 表达的是“项目导航栏默认宽度”。
- `InspectorPropertyControlColumnStart()` 表达的是“Inspector 控件列起点”。
- `PanelSplitterHitThickness()` 表达的是“splitter 命中区宽度”。
这比直接写 `248.0f``236.0f``4.0f` 更接近商业编辑器的工程实践,因为当 UI 逐步复杂起来后,真正难维护的从来不是控件本身,而是这些散落的视觉约束。
## 当前 token 分组
`editor/src/UI/StyleTokens.h` 的实现,当前 token 大致可以分成下面几类:
| 类别 | 代表 token | 主要消费者 |
|------|-------------|------------|
| Dock host / tab | `DockHostFramePadding()``DockTabSelectedColor()` | `DockHostStyle``DockTabBarChrome` |
| 面板与工具栏 | `PanelWindowPadding()``ToolbarPadding()` | `PanelChrome`、各类 panel toolbar |
| Project 浏览器 | `ProjectNavigationDefaultWidth()``AssetGridSpacing()` | `ProjectPanel``Widgets` |
| TreeView 导航树 | `NavigationTreeIconSize()``NavigationTreePrefixWidth()` | `TreeView``HierarchyPanel``ProjectPanel` |
| Inspector / 属性布局 | `InspectorPropertyControlColumnStart()``ControlFramePadding()` | `PropertyLayout``ScalarControls``VectorControls``PropertyGrid` |
| Popup / Combo | `PopupWindowPadding()``ComboPopupBackgroundColor()` | `Core``Widgets` |
| 资产卡片与图标 | `AssetTileSize()``BuiltInFolderIconBodyColor()` | `Widgets``BuiltInIcons` |
| Console / 状态栏 | `ConsoleRowHoverFillColor()``MenuBarStatusIdleColor()` | `Widgets`、菜单栏与 Console UI |
此外,`StyleTokens.h` 里还定义了:
- `struct TreeViewStyle`
- `NavigationTreeStyle()`
- `HierarchyTreeStyle()`
- `ProjectFolderTreeStyle()`
它们是树视图层的“组合 token”不是单个数值而是一组可复用的树节点视觉约束。
## 当前实现行为
按当前源码,这一层有几个很重要的特点:
- token 目前全部是 header-only inline 函数,不是配置资产,也不是运行时可热更新的数据表。
- 很多 helper 会把这里的 token 当默认值,而不是自己再声明一套局部常量。
- `PropertyLayout``SplitterChrome``DockTabBarChrome``BuiltInIcons``Widgets` 都直接依赖这里的 token。
- 某些 token 之间存在设计上的复用关系,例如 `PanelDividerColor()` 当前直接复用 splitter 的 idle 颜色。
这意味着 `StyleTokens.h` 不是“装饰性文件”,而是整个 Editor UI 复用层的上游依赖。
## 设计说明
这是商业编辑器 UI 非常推荐的做法。
不要在控件代码里到处写 `6.0f``0.24f``104.0f` 这种魔法数字,而应先抽象成 token。
为什么这层很重要?
收益包括:
- 当你在重构 `ProjectPanel` 时,不应该顺手发明一套新的资产卡片尺寸。
- 当你在做 `HierarchyPanel` 的树前缀图标时,不应该自己再决定一套图标槽宽。
- 当你在优化 Inspector 的密度时,真正应该改的是 token而不是 5 个不同 helper 里的重复常量。
- 调整风格时能集中修改
- 命名本身就表达设计意图
- 高层 widget 与具体数值解耦
这就是商业级编辑器常说的“设计系统落地到代码层”的意义。对于 XCEngine 来说,这一层还没有演化成完整的主题资产系统,但它已经承担了相同的架构角色。
## 与其他 UI helper 的关系
- `PropertyLayout` 使用它定义属性行的列宽、label inset 和 frame padding。
- `TreeView` 使用它定义缩进、前缀插槽宽度和图标尺寸。
- `SplitterChrome``DividerChrome` 使用它定义边界线和分隔条视觉。
- `Widgets` 使用它定义资产卡片、面包屑、组件 section 和各种通用控件的视觉细节。
- `BuiltInIcons` 使用它定义文件 / 文件夹 fallback 图标颜色。
从依赖方向上说,这一层应该尽量保持在 UI 栈的较底部,避免反向依赖更高层的业务 widget。
## 当前限制
- 仍然是 header inline 常量函数,而不是数据驱动主题系统
- token 数量已经较多,后续可能需要再分层
- 当前主要围绕唯一默认主题设计
- 当前仍然是“写死在代码里的默认主题”,不是运行时可切换的主题资源系统
- token 命名已经带有明显的面板语义,例如 `ProjectNavigationPanePadding()`,说明它还是偏应用内设计系统,而不是完全抽象的通用 design token。
- 还没有颜色模式切换、用户主题文件或样式编辑器一类更高阶能力。
## 相关文档
- [UI](../UI.md)
- [BaseTheme](../BaseTheme/BaseTheme.md)
- [PanelChrome](../PanelChrome/PanelChrome.md)
- [Widgets](../Widgets/Widgets.md)
- [DockHostStyle](../DockHostStyle/DockHostStyle.md)
- [PropertyLayout](../PropertyLayout/PropertyLayout.md)
- [TreeView](../TreeView/TreeView.md)
- [SplitterChrome](../SplitterChrome/SplitterChrome.md)

View File

@@ -0,0 +1,122 @@
# TreeView
**命名空间**: `XCEngine::Editor::UI`
**类型**: `header-helper + class + struct`
**源文件**: `editor/src/UI/TreeView.h`
**描述**: 提供编辑器树形视图的统一节点状态、前缀绘制槽、交互回调和展开状态持久化机制。
## 概述
`TreeView.h` 是当前 Editor UI 重构里最重要的公共基础设施之一。它把原本很容易散落在 `HierarchyPanel``ProjectPanel` 中的树节点绘制逻辑抽成一层统一接口,让不同面板能够共享:
- 相同的树节点视觉风格。
- 相同的展开 / 收起行为。
- 相同的前缀图标插槽模型。
- 相同的点击、双击、右键、拖拽扩展接入点。
如果从使用体验上理解,它很接近商业编辑器中“导航树控件”的内建基础层。`HierarchyPanel``ProjectPanel` 的左侧目录树并没有各写一套独立的 tree widget而是都基于这层构建。
## 公开类型与函数
| 成员 | 说明 |
|------|------|
| `class TreeViewState` | 保存节点展开状态的内存态容器。 |
| `TreeNodeOptions` | 控制选中、叶子节点、默认展开、点击展开策略等基础选项。 |
| `TreeNodeResult` | 返回节点本帧是否打开、是否点击、是否双击、是否右键等交互结果。 |
| `TreeNodePrefixContext` | 给前缀绘制回调提供绘制区域、选中状态和 hover 状态。 |
| `TreeNodePrefixSlot` | 定义一个可选的前缀绘制槽。 |
| `TreeNodeCallbacks` | 定义交互回调和额外渲染回调。 |
| `TreeNodeDefinition` | 组合节点选项、持久化 key、样式、前缀槽与回调。 |
| `ResetTreeLayout()` | 清空当前树布局的缩进栈,开始一棵新的树。 |
| `DrawTreeNode(...)` | 绘制节点并返回本帧结果。 |
| `EndTreeNode()` | 与打开状态的节点配对,弹出一层树缩进。 |
## TreeViewState 的角色
`TreeViewState` 当前只做一件事:根据 `std::string_view key` 保存节点展开状态。
它不是场景数据,也不是项目数据,只是某个 UI 实例自己的展开状态缓存。因此:
- `HierarchyPanel` 用它记住实体树哪些节点已经展开。
- `ProjectPanel` 用它记住目录树哪些文件夹已经展开。
当前实现采用 `std::unordered_map<std::string, bool>`,所以最好的 `persistenceKey` 是稳定且能跨帧复现的标识,例如:
- 场景对象的 UUID
- 资源目录的完整路径
## 当前实现行为
`editor/src/UI/TreeView.h` 的实现,当前版本有几个关键事实:
- 树缩进不是保存在某个对象里,而是保存在一个静态 `TreeIndentStack()` 中。
- 因此每次绘制一棵新的根树之前都应该先调用 `ResetTreeLayout()`
- `DrawTreeNode(...)` 若返回 `open = true`,调用方在绘制完所有子节点后必须调用 `EndTreeNode()` 与之配对。
- 节点背景当前直接使用 `ImGuiCol_Header` / `HeaderHovered` / `HeaderActive` 这一组颜色绘制选中与 hover 态。
- 展开箭头由 `Core.h` 里的 `DrawDisclosureArrow(...)` 手工绘制。
- prefix slot 允许调用方在标签前插入小图标、状态灯或其他自定义绘制内容。
- `callbacks.onInteraction` 适合接选择、右键菜单、双击重命名等语义。
- `callbacks.onRenderExtras` 适合接拖拽源 / 拖拽目标之类必须紧跟节点绘制的额外逻辑。
这说明它不是对 `ImGui::TreeNodeEx(...)` 的轻封装,而是自己接管了树节点的大部分视觉和交互行为。
## 设计说明
为什么要单独造这一层,而不是每个面板直接用 ImGui 原生 tree API
- `Hierarchy``Project` 都需要统一的左侧导航树风格,这种复用非常适合抽成共享 helper。
- 商业编辑器中的树视图往往不仅是“文本 + 箭头”,还需要图标前缀、整行高亮、拖放热点、重命名切换等语义。
- 一旦把节点定义、展开状态和回调插槽标准化,上层面板代码就能更聚焦于业务对象,而不是 UI 细节。
如果和 Unity 对照可以把它理解成“Hierarchy / Project 导航树的共用控件层”,而不是某个特定面板的局部技巧。
## 典型用法
```cpp
UI::ResetTreeLayout();
UI::TreeNodeDefinition node;
node.options.selected = isSelected;
node.options.leaf = !hasChildren;
node.persistenceKey = stablePath;
node.style = UI::ProjectFolderTreeStyle();
node.prefix.width = UI::NavigationTreePrefixWidth();
node.prefix.draw = DrawFolderPrefix;
node.callbacks.onInteraction = [&](const UI::TreeNodeResult& result) {
if (result.clicked) {
NavigateToFolder();
}
};
const UI::TreeNodeResult result =
UI::DrawTreeNode(&m_treeState, folder.get(), folder->name.c_str(), node);
if (result.open) {
DrawChildren();
UI::EndTreeNode();
}
```
## 生命周期与线程语义
- `TreeViewState` 由具体面板持有,通常作为成员变量存在于面板实例生命周期内。
- 整套 API 都依赖当前 ImGui 帧状态,只能在 UI 线程使用。
- 它不负责把展开状态写入磁盘或布局文件;当前只是内存级持久化。
## 当前限制
- 静态 `TreeIndentStack()` 让它更像“约定式 helper”而不是完全可重入的小部件对象。
- 没有键盘导航、滚动到可见、虚拟化大树等更高级的树控件能力。
- 展开状态目前只保存在面板实例中,重启编辑器后不会自动恢复。
- 当前回调模型偏即时模式,适合编辑器内部使用,但不是声明式 UI 框架。
## 相关文档
- [UI](../UI.md)
- [StyleTokens](../StyleTokens/StyleTokens.md)
- [BuiltInIcons](../BuiltInIcons/BuiltInIcons.md)
- [HierarchyPanel](../../panels/HierarchyPanel/HierarchyPanel.md)
- [ProjectPanel](../../panels/ProjectPanel/ProjectPanel.md)

View File

@@ -4,47 +4,106 @@
**类型**: `submodule`
**描述**: 编辑器 ImGui 基础设施、主题、控件与面板绘制辅助层
**描述**: 编辑器基于 Dear ImGui 的 UI 基础设施层,负责会话、主题 token、面板 chrome、树视图、属性布局和通用控件封装
## 概述
`UI` 子模块当前内容很多,但核心方向很清楚:
`XCEngine::Editor::UI` 不是单个 widget 库,而是一组逐层叠加的编辑器 UI 基建。它的目标是把 Dear ImGui 这种非常自由、非常底层的即时模式 API 收束成一套可维护的“编辑器内建控件层”。
- 维护 ImGui 会话与布局文件
- 封装统一视觉主题
- 提供属性面板、工具栏、菜单、场景状态等 UI 辅助
当前源码里,这一层大致可以分成四层:
已文档化的核心页面:
1. **会话与后端层**
负责 ImGui 上下文、ini 持久化、后端桥接和纹理描述符分配。
2. **主题与设计 token 层**
负责颜色、尺寸、间距、dock host 风格与常用视觉常量。
3. **chrome 与布局层**
负责面板窗口、工具栏、树视图、splitter、divider、属性行布局和自定义 dock 标签栏。
4. **语义化控件层**
负责属性网格、资产卡片、面包屑、弹窗、组件折叠段和各种编辑器专用小部件。
- [ImGuiSession](ImGuiSession/ImGuiSession.md)
- [ImGuiBackendBridge](ImGuiBackendBridge/ImGuiBackendBridge.md)
- [BaseTheme](BaseTheme/BaseTheme.md)
- [StyleTokens](StyleTokens/StyleTokens.md)
- [Core](Core/Core.md)
- [PanelChrome](PanelChrome/PanelChrome.md)
- [Widgets](Widgets/Widgets.md)
- [PopupState](PopupState/PopupState.md)
- [ScalarControls](ScalarControls/ScalarControls.md)
- [VectorControls](VectorControls/VectorControls.md)
- [PropertyGrid](PropertyGrid/PropertyGrid.md)
- [DockHostStyle](DockHostStyle/DockHostStyle.md)
- [ConsoleFilterState](ConsoleFilterState/ConsoleFilterState.md)
- [ConsoleLogFormatter](ConsoleLogFormatter/ConsoleLogFormatter.md)
- [SceneStatusWidget](SceneStatusWidget/SceneStatusWidget.md)
- [AboutEditorDialog](AboutEditorDialog/AboutEditorDialog.md)
这类分层方式非常接近商业级游戏引擎编辑器的组织方式。原因很简单:如果每个面板都直接拼原始 ImGui 调用,代码很快就会变成不可维护的样式杂糅体;而先建设统一 UI 层,后续任何面板重构、主题调整和交互升级都会容易很多。
`UI/UI.h` 本身只是聚合入口,真正值得阅读的是这些分层 helper
## 聚合头文件
- `ImGuiSession + ImGuiBackendBridge` 负责上下文与后端绑定
- `BaseTheme + StyleTokens` 负责视觉规范
- `Core + PanelChrome + Widgets` 负责通用交互壳层
- `ScalarControls + VectorControls + PropertyGrid` 负责 Inspector 表单体验
**源文件**: `editor/src/UI/UI.h`
这样拆分的好处,是把 Dear ImGui 容易失控的即时模式代码收束成一套可复用部件库
`UI.h` 当前是一个标准的 umbrella header。它的职责是把 Editor UI 常用 helper 聚合到同一个入口,而不是声明新的运行时类型
因此这页文档直接承担 `UI.h` 的说明职责,不再额外创建一个重复的类型页。
## 当前架构重点
结合当前 `editor/src/UI/**` 与最近一次 Editor UI 重构,以下几层是现在最值得读的核心:
- `ImGuiSession + ImGuiBackendBridge`
管理 ImGui 上下文、DPI、SRV 描述符与窗口后端对接。
- `BaseTheme + StyleTokens + DockHostStyle`
管理设计 token 和 dock host 外观,是全局视觉风格的起点。
- `Core + DividerChrome + SplitterChrome + DockTabBarChrome + PanelChrome`
管理窗口 chrome、底边线、拖拽分隔条与自定义 dock 标签栏。
- `TreeView + BuiltInIcons`
管理层级树 / 目录树的共享节点表现与图标前缀。
- `PropertyLayout + ScalarControls + VectorControls + PropertyGrid`
管理 Inspector 的属性布局、标量控件、向量控件和高层属性编辑入口。
- `Widgets + PopupState + SceneStatusWidget + AboutEditorDialog`
提供更接近编辑器业务语义的通用 widget。
这次重构里,`TreeView``PropertyLayout``BuiltInIcons``DockTabBarChrome` 是最明显的新基础设施,它们共同把原先分散在具体面板中的 UI 技巧沉淀成了复用层。
## 设计说明
如果把它和 Unity 一类成熟编辑器对照,可以把 `Editor::UI` 理解成“编辑器内部控件系统”,而不是游戏运行时 UI 框架:
- 它面向工具开发,不面向游戏内 HUD。
- 它可以为编辑器体验服务而使用较强约束的 helper。
- 它允许把某些风格、布局和交互策略写死在共享层,以换取整个工具界面的统一性。
这种设计的好处是:
- 面板作者写的代码更接近业务语义,而不是样式拼装。
- 主题、间距、树视图和 Inspector 布局能在全局范围内保持一致。
- 面板之间可以共享拖拽、上下文菜单、资产卡片、树节点前缀等成熟模式。
代价也很明确:
- 当前 UI 层明显偏 Editor 私有实现,不是面向外部插件的稳定 ABI。
- 很多 helper 仍然是 header-only inline 形式,接口演化速度会比较快。
- 当前实现强依赖 Windows + D3D12 + Dear ImGui 的编辑器运行路径。
## 头文件
- [AboutEditorDialog](AboutEditorDialog/AboutEditorDialog.md) - `AboutEditorDialog.h`,关于对话框相关 UI。
- [BaseTheme](BaseTheme/BaseTheme.md) - `BaseTheme.h`,编辑器基础主题安装。
- [BuiltInIcons](BuiltInIcons/BuiltInIcons.md) - `BuiltInIcons.h`,内置资源/对象图标系统。
- [ConsoleFilterState](ConsoleFilterState/ConsoleFilterState.md) - `ConsoleFilterState.h`Console 过滤状态。
- [ConsoleLogFormatter](ConsoleLogFormatter/ConsoleLogFormatter.md) - `ConsoleLogFormatter.h`Console 日志格式化。
- [Core](Core/Core.md) - `Core.h`,底层 ImGui helper 与 popup chrome 包装。
- [DividerChrome](DividerChrome/DividerChrome.md) - `DividerChrome.h`,统一分隔线绘制。
- [DockHostStyle](DockHostStyle/DockHostStyle.md) - `DockHostStyle.h`dock host 风格压栈。
- [DockTabBarChrome](DockTabBarChrome/DockTabBarChrome.md) - `DockTabBarChrome.h`,自定义 dock 标签栏。
- [ImGuiBackendBridge](ImGuiBackendBridge/ImGuiBackendBridge.md) - `ImGuiBackendBridge.h`ImGui 与 D3D12 之间的桥接层。
- [ImGuiSession](ImGuiSession/ImGuiSession.md) - `ImGuiSession.h`ImGui 会话生命周期。
- [PanelChrome](PanelChrome/PanelChrome.md) - `PanelChrome.h`,面板窗口 / 工具栏 / 内容区 RAII 外壳。
- [PopupState](PopupState/PopupState.md) - `PopupState.h`,延迟弹窗与目标型弹窗状态。
- [PropertyGrid](PropertyGrid/PropertyGrid.md) - `PropertyGrid.h`Inspector 级属性编辑入口。
- [PropertyLayout](PropertyLayout/PropertyLayout.md) - `PropertyLayout.h`,属性行几何布局层。
- [ScalarControls](ScalarControls/ScalarControls.md) - `ScalarControls.h`,标量属性控件。
- [SceneStatusWidget](SceneStatusWidget/SceneStatusWidget.md) - `SceneStatusWidget.h`,场景状态部件。
- [SplitterChrome](SplitterChrome/SplitterChrome.md) - `SplitterChrome.h`,分隔条交互与绘制。
- [StyleTokens](StyleTokens/StyleTokens.md) - `StyleTokens.h`Editor UI 设计 token 中心。
- [TreeView](TreeView/TreeView.md) - `TreeView.h`,树视图共享基础设施。
- [VectorControls](VectorControls/VectorControls.md) - `VectorControls.h`,向量属性控件。
- [Widgets](Widgets/Widgets.md) - `Widgets.h`,资产卡片、面包屑、组件折叠段等通用 widget。
## 当前实现边界
- 这是 Editor 应用层 UI不是运行时 `engine/include/XCEngine` 风格的公共引擎 API。
- 当前很多接口仍然直接暴露 ImGui 类型,如 `ImDrawList``ImVec2``ImGuiID`
- 自定义 dock 标签栏、树视图和图标系统都明显面向当前编辑器产品形态,而不是通用 GUI 框架。
## 相关文档
- [Editor 模块](../Editor.md)
- [Application](../Application/Application.md)
- [Platform](../Platform/Platform.md)
- [Editor](../Editor.md)
- [Layout](../Layout/Layout.md)
- [ComponentEditors](../ComponentEditors/ComponentEditors.md)
- [Editor Architecture And Workflow](../../../_guides/Editor/Editor-Architecture-And-Workflow.md)

View File

@@ -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)

View File

@@ -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)