chore: sync workspace state
This commit is contained in:
@@ -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 headers;Editor 这组文档更依赖目录并行约束、链接校验和人工核对源码行为。
|
||||
|
||||
## 目录
|
||||
|
||||
- [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)
|
||||
|
||||
107
docs/api/XCEngine/Editor/UI/BuiltInIcons/BuiltInIcons.md
Normal file
107
docs/api/XCEngine/Editor/UI/BuiltInIcons/BuiltInIcons.md
Normal 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)
|
||||
73
docs/api/XCEngine/Editor/UI/DividerChrome/DividerChrome.md
Normal file
73
docs/api/XCEngine/Editor/UI/DividerChrome/DividerChrome.md
Normal 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)
|
||||
103
docs/api/XCEngine/Editor/UI/DockTabBarChrome/DockTabBarChrome.md
Normal file
103
docs/api/XCEngine/Editor/UI/DockTabBarChrome/DockTabBarChrome.md
Normal 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)
|
||||
105
docs/api/XCEngine/Editor/UI/PropertyLayout/PropertyLayout.md
Normal file
105
docs/api/XCEngine/Editor/UI/PropertyLayout/PropertyLayout.md
Normal 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)
|
||||
88
docs/api/XCEngine/Editor/UI/SplitterChrome/SplitterChrome.md
Normal file
88
docs/api/XCEngine/Editor/UI/SplitterChrome/SplitterChrome.md
Normal 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)
|
||||
@@ -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)
|
||||
|
||||
122
docs/api/XCEngine/Editor/UI/TreeView/TreeView.md
Normal file
122
docs/api/XCEngine/Editor/UI/TreeView/TreeView.md
Normal 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)
|
||||
@@ -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)
|
||||
|
||||
@@ -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