diff --git a/docs/used/NewEditor_ColorPicker独立面板重构计划_2026-04-20.md b/docs/used/NewEditor_ColorPicker独立面板重构计划_2026-04-20.md deleted file mode 100644 index 0a814e00..00000000 --- a/docs/used/NewEditor_ColorPicker独立面板重构计划_2026-04-20.md +++ /dev/null @@ -1,222 +0,0 @@ -# NewEditor Color Picker 独立面板重构计划 -日期: `2026-04-20` - -## 1. 目标 - -本次只解决 `new_editor` 当前颜色选择器的根源性架构问题。 - -目标不是继续修补 `Inspector` 里的颜色字段弹窗,而是把 `Color Picker` 重建为一个真正的独立面板,并复用现有的分离标签页 / 分离窗口主线。 - -完成后应满足: - -- `Inspector` 里的颜色字段只负责显示当前值和发起打开请求 -- 颜色编辑 UI 只存在于独立的 `Color Picker` 面板中 -- `Color Picker` 通过现有 detached window 基础设施打开 -- 不再依赖 `PropertyGrid` 行内 popup 生命周期 -- 不再为了颜色选择器单开一套平行窗口系统 - -## 2. 根因结论 - -### 2.1 当前颜色选择器的宿主边界是错的 - -现在的颜色选择器被建模成了 `PropertyGrid ColorField` 的内联 popup: - -- 字段自己持有 popup 开关状态 -- 字段自己做 popup 布局 -- 字段自己做 popup 命中测试 -- 字段自己做 popup 拖动交互 -- `PropertyGrid` 自己参与 popup 关闭和分发 - -这说明当前颜色选择器不是一个独立工具面板,而是被硬塞进了字段控件层。 - -### 2.2 真正缺的不是窗口能力,而是 panel 身份 - -项目里已经有完整的 detached panel / detached window 主线: - -- `UIEditorPanelRegistry` -- `UIEditorPanelContentHost` -- `UIEditorWindowWorkspaceController` -- `EditorWindowWorkspaceCoordinator` -- `EditorWindowHostRuntime` - -所以问题不在“怎么开一个新窗口”,而在“颜色选择器根本没有被建模成一个 panel”。 - -### 2.3 不能靠把 Color Picker 塞回主 workspace 树解决 - -现有 `OpenPanel` 只适用于已经存在于 workspace 树和 session 里的 panel。 - -如果为了颜色选择器去伪造一个隐藏节点,再靠打开命令拉出来,本质上还是绕路: - -- 主 workspace 会被无关工具污染 -- session 约束会变复杂 -- 工具窗口语义会和主编辑布局耦合 - -正确做法是直接用 panel descriptor 构造一个单面板 detached workspace,并交给现有窗口协调器同步。 - -## 3. 重构原则 - -本次必须遵守: - -1. 不再继续扩展 `PropertyGrid` 的颜色 popup 分支 -2. 不再让 `ColorField` 拥有独立工具 UI 的完整生命周期 -3. 优先复用现有 panel / detached window 基础设施 -4. 不引入第二套平行“tool window”系统 -5. 不把 `Color Picker` 强行塞进默认主 workspace 布局 - -## 4. 目标结构 - -重构后应形成下面的边界: - -### 4.1 Inspector ColorField - -职责只保留: - -- 显示 swatch / 文本值 -- 响应点击 -- 发出“打开 Color Picker”的请求 -- 在目标匹配时读取共享颜色状态并回写组件字段 - -不再负责: - -- popup 布局 -- popup 绘制 -- popup 拖动 -- popup 关闭 - -### 4.2 EditorContext 中的共享工具状态 - -新增共享 `Color Picker` 状态,放在 `EditorContext`,作为 Inspector 与 Color Picker 面板之间的唯一数据桥: - -- 当前是否激活 -- 当前编辑颜色 -- 是否显示 alpha -- 当前 inspector 目标 -- 打开独立面板请求 -- 版本号 / 修改序号 - -### 4.3 ColorPickerPanel - -新增 `HostedContent panel`: - -- 作为真正的颜色编辑 UI 宿主 -- 读取并更新共享 `Color Picker` 状态 -- 不依赖 `PropertyGrid` 内联 popup - -### 4.4 Window / Workspace 层 - -新增一条“直接把某个 panel 打开到新 detached window”能力: - -- 输入 `panelId` -- 用 panel descriptor 直接构造单 panel detached workspace -- 不要求该 panel 先存在于主 workspace 树 -- 复用现有 detached window 同步链路 - -## 5. 执行步骤 - -### 步骤 1:固化共享状态模型 - -- 把 `EditorColorPickerToolState` 接入 `EditorContext` -- 提供统一 getter -- 在 `Initialize` 时正确重置 - -完成标准: - -- 颜色选择器状态不再挂在 inspector 局部状态或字段视觉状态里 - -### 步骤 2:注册正式的 Color Picker panel - -- 在 `EditorPanelIds` 中保留正式 panel id/title -- 在 `EditorShellAssetBuilder` 中把 `Color Picker` 注册为 `HostedContent` -- 不加入默认主 workspace tree - -完成标准: - -- `Color Picker` 在架构层面是一个真实 panel,而不是字段副产物 - -### 步骤 3:扩展 detached panel 打开链路 - -- 给 `UIEditorWindowWorkspaceController` 增加“按 panelId 打开独立新窗口”的操作 -- 用 panel descriptor 构造 `UIEditorWorkspaceExtractedPanel` -- 复用 - - `BuildUIEditorDetachedWorkspaceFromExtractedPanel` - - `BuildUIEditorDetachedWorkspaceSessionFromExtractedPanel` -- 给 `EditorWindowTransferRequests` - - `EditorWindowFrameOrchestrator` - - `EditorWindowWorkspaceCoordinator` - 增加对应请求传递与处理 - -完成标准: - -- 任意已注册 panel 都可以直接被拉起为 detached window -- 这条能力不依赖 tab drag,也不依赖主 workspace 里预先存在节点 - -### 步骤 4:实现 ColorPickerPanel - -- 新增独立 `ColorPickerPanel` -- 接入 `EditorShellRuntime` -- 通过 hosted content 挂载 -- 面板内容直接消费共享 `Color Picker` 状态 - -实现策略: - -- 优先复用现有颜色编辑 UI 的绘制 / 交互能力 -- 但这些能力必须由 `ColorPickerPanel` 驱动,而不是由 `PropertyGrid` 行内字段驱动 - -完成标准: - -- 真正的颜色编辑 UI 已经迁移到独立 panel - -### 步骤 5:重构 Inspector 接线 - -- `InspectorPanel` 访问 `EditorContext` 中的共享 `Color Picker` 状态 -- 颜色字段点击时: - - 写入目标信息 - - 写入初始颜色 - - 请求打开 detached `Color Picker` panel -- 每帧按目标匹配把共享颜色回写到对应字段 -- 继续沿用原有组件 editor 的 `ApplyFieldValue` 落地逻辑 - -完成标准: - -- Inspector 只做请求与回写,不再拥有颜色选择器 popup - -### 步骤 6:清理 PropertyGrid 颜色字段逻辑 - -- 移除 `ProcessColorFieldEvent` 对 popup 生命周期的依赖 -- 停止让 `UIEditorColorFieldInteraction` 在 property grid 里开 popup -- 颜色字段只保留选择 / 激活 / 打开请求语义 - -完成标准: - -- `PropertyGrid` 不再承载颜色工具面板 - -### 步骤 7:编译与验证 - -- 更新 `new_editor/CMakeLists.txt` -- 编译 `XCUIEditorApp` -- 验证以下路径: - - Inspector 点击颜色字段后拉起独立窗口 - - 修改颜色可实时或按预期回写 - - 重复点击同一字段不会产生混乱状态 - - 切换对象后目标绑定不串 - - 关闭颜色窗口后状态正常 - -## 6. 非目标 - -本次明确不做: - -- 顺手重写整个 `Inspector` -- 顺手把其他 popup 全改成独立窗口 -- 为颜色选择器发明独立于 panel/window 体系之外的新框架 -- 继续保留“字段内 popup + 独立窗口”双轨实现 - -## 7. 完成判定 - -只有下面条件全部满足,才算这次工作完成: - -- `Color Picker` 已经是正式 panel -- `Color Picker` 通过现有 detached window 主线打开 -- `Inspector ColorField` 不再持有 popup 主逻辑 -- `PropertyGrid` 不再承载颜色工具 UI 生命周期 -- 颜色值同步路径清晰,归属明确 -- 工程可编译,可运行,可验证 diff --git a/docs/used/NewEditor_InspectorLayout_RootRefactorPlan_2026-04-21.md b/docs/used/NewEditor_InspectorLayout_RootRefactorPlan_2026-04-21.md deleted file mode 100644 index 2a546a1e..00000000 --- a/docs/used/NewEditor_InspectorLayout_RootRefactorPlan_2026-04-21.md +++ /dev/null @@ -1,89 +0,0 @@ -# NewEditor Inspector Layout Root Refactor Plan -Date: `2026-04-21` - -## Context - -This plan targets the `new_editor` Inspector field layout path only. - -Current symptom: -- Inspector field controls do not follow one explicit column policy. -- Different field types shift or compress at different thresholds. -- `Transform` and the fields below it stop aligning once the Inspector becomes narrow. - -Current root cause: -- Shared row layout code in `UIEditorFieldRowLayout` is guessing when a caller is "Inspector-like" and silently replacing the caller's requested minimum control width. -- That guess is based on generic layout numbers such as `controlColumnStart` and `labelControlGap`, which is an invalid ownership boundary. -- Vector fields also still carry a dead prefix palette chain that no longer affects rendering after the visible background was moved to `componentRects`. - -## Files In Scope - -- `new_editor/include/XCEditor/Widgets/UIEditorFieldRowLayout.h` -- `new_editor/src/Widgets/UIEditorFieldRowLayout.cpp` -- `new_editor/include/XCEditor/Fields/UIEditorPropertyGrid.h` -- `new_editor/include/XCEditor/Fields/UIEditorFieldStyle.h` -- `new_editor/src/Fields/UIEditorFieldStyle.cpp` -- Hosted Inspector field headers under `new_editor/include/XCEditor/Fields/` -- Hosted Inspector field sources under `new_editor/src/Fields/` - -## Refactor Goals - -1. Remove all implicit Inspector detection from shared row layout. -2. Make shared control-column width an explicit metric owned by PropertyGrid and forwarded to hosted fields. -3. Keep hosted field layout behavior consistent by policy instead of per-field hidden overrides. -4. Remove dead vector prefix palette data that no longer drives rendering. -5. Rebuild `XCUIEditor.exe` after the refactor and keep the worktree changes scoped to `new_editor`. - -## Implementation Plan - -### Stage 1: Make The Contract Explicit - -Add one explicit metric for hosted shared-column reservation: -- `sharedControlColumnMinWidth` - -Propagation chain: -- `UIEditorPropertyGridMetrics` -- hosted field metrics (`Bool`, `Number`, `Text`, `Enum`, `Color`, `Object`, `Asset`, `Vector2`, `Vector3`, `Vector4`) -- `UIEditorFieldRowLayoutMetrics` - -Rules: -- Default value outside Inspector stays `0.0f` -- Inspector-owned PropertyGrid metrics set the shared width explicitly -- Shared row layout consumes this field directly and never infers Inspector mode from unrelated metrics - -### Stage 2: Remove Hidden Width Guessing - -Replace the current behavior in `UIEditorFieldRowLayout.cpp`: -- delete the implicit `ResolveHostedControlMinimumWidth(...)` path -- delete the `inspectorHostedControlMinWidth` token -- make reserved control width depend only on explicit row-layout metrics and the caller-provided value - -Expected result: -- the row-layout contract becomes deterministic -- hosted fields stop getting different fallback behavior because of hidden special-cases - -### Stage 3: Clean Vector Dead Code - -Remove `prefixColor` and `prefixBorderColor` from: -- inspector field tokens -- vector field palette structs -- vector palette resolution code -- PropertyGrid palette builder forwarding - -Reason: -- those fields no longer affect visible rendering after the component background moved to `componentRects` - -### Stage 4: Validation - -Validation steps: -- compile `XCUIEditorApp` in `Debug` -- if the editor process is locking the output exe, kill `XCUIEditor` first -- verify the rebuilt output is `build/new_editor/Debug/XCUIEditor.exe` - -## Done Criteria - -This refactor is complete when: -- shared row layout contains no Inspector guessing logic -- Inspector column width policy is explicit in metrics -- hosted field metric forwarding is consistent for all field types -- vector dead palette chain is removed -- `XCUIEditor.exe` rebuild succeeds diff --git a/docs/used/NewEditor_Inspector字段编辑内核重构计划_2026-04-20.md b/docs/used/NewEditor_Inspector字段编辑内核重构计划_2026-04-20.md deleted file mode 100644 index f1e29f63..00000000 --- a/docs/used/NewEditor_Inspector字段编辑内核重构计划_2026-04-20.md +++ /dev/null @@ -1,408 +0,0 @@ -# NewEditor Inspector字段编辑内核重构计划 -日期: `2026-04-20` - -## 1. 文档定位 - -这份计划只解决 `new_editor` Inspector 内联字段编辑这条链路的根因问题,不再继续沿着当前 `number / text / vector` 各自修补的路线前进。 - -覆盖范围: -- `new_editor/include/XCEditor/Fields/UIEditorNumberField*.h` -- `new_editor/include/XCEditor/Fields/UIEditorTextField*.h` -- `new_editor/include/XCEditor/Fields/UIEditorVector{2,3,4}Field*.h` -- `new_editor/include/XCEditor/Fields/UIEditorPropertyGridInteraction.h` -- `new_editor/src/Fields/UIEditorPropertyGridInteraction.cpp` -- `new_editor/src/Fields/PropertyGridInteractionInternal.h` -- `new_editor/src/Fields/VectorFieldInteractionInternal.h` -- `new_editor/include/XCEditor/Widgets/UIEditorTextLayout.h` - -对标参考: -- [editor/src/UI/ScalarControls.h](/D:/Xuanchi/Main/XCEngine/editor/src/UI/ScalarControls.h) -- [editor/src/UI/VectorControls.h](/D:/Xuanchi/Main/XCEngine/editor/src/UI/VectorControls.h) -- [editor/src/UI/PropertyGrid.h](/D:/Xuanchi/Main/XCEngine/editor/src/UI/PropertyGrid.h) - -这次重构的目标不是“再把几个 bug 修掉”,而是把字段编辑从现在这套分裂的状态机里拔出来,收敛成一条统一主链。 - -## 2. 根因结论 - -### 2.1 当前没有共享的字段编辑内核 - -现在的 `number`、`text`、`vector` 都各自维护一套点击、双击、拖动、文本编辑、提交、取消、光标闪烁逻辑。 - -结果是: -- 同一个交互语义在多个文件里重复实现 -- 一个行为改动需要同时改多个状态机 -- 修一个字段,另外几个字段容易继续偏离 -- 小问题会不断演化成“再加一点同步代码” - -这不是单点 bug,而是缺少共享内核导致的结构性问题。 - -### 2.2 编辑状态的所有权被拆成了多份 - -当前至少存在下面几层同时持有“编辑相关状态”: -- `UIEditorPropertyGridInteractionState` -- `UIEditorNumberFieldInteractionState` -- `UIEditorTextFieldInteractionState` -- `UIEditorVector{2,3,4}FieldInteractionState` -- `UIEditor*FieldState` -- `UITextInputState` -- `UIPropertyEditModel` - -这会直接导致: -- `caret` 既在文本输入状态里存在,又在字段视觉状态里存在 -- `displayText`、`stagedValue`、实际字段值分散在不同对象里 -- 一个字段是否处于编辑态,不是单一真值源 -- 某些帧里逻辑已经进入编辑,但渲染层还没完全同步 - -“双击后光标不立刻出现”就是这种分裂所有权的典型症状。 - -### 2.3 PropertyGrid 现在不是统一编排,而是在按字段类型手工分发 - -`UIEditorPropertyGridInteraction.cpp` 当前的工作方式,本质上是: -- 先跑 number 分支 -- 再跑 text 分支 -- 再跑 vector 分支 -- 再补一套全局编辑逻辑 -- 再补焦点、弹窗、键盘导航收尾 - -这会带来两个严重问题: -- 行为依赖处理顺序,而不是依赖明确的领域规则 -- `PropertyGrid` 本身已经知道太多字段细节,逐渐变成新的 God Object - -### 2.4 各字段把“领域语义”和“交互语义”混在了一起 - -对于 number / vector,真正属于字段自身的职责只有这些: -- 格式化值 -- 解析文本 -- 步进规则 -- 范围裁剪 -- 组件数量与索引规则 - -但现在每个字段实现同时还承担了: -- 指针命中 -- 双击检测 -- 拖动启动阈值 -- 拖拽中的增量计算 -- 文本缓冲同步 -- 光标闪烁同步 -- 提交/取消时机 - -这导致“字段种类一变,整套交互都要重写”。 - -### 2.5 当前结构已经出现了“共享逻辑伪装成临时 Internal helper”的迹象 - -`VectorFieldInteractionInternal.h` 这种大模板式内部头文件,说明代码已经在强行复用,但复用方式不稳定: -- 共享的是实现细节,不是明确边界 -- 逻辑被放进 header,难以形成单一实现入口 -- vector 虽然共享了一部分,但 number / text 仍然各走各的 - -这不是正式内核,只是重复逻辑开始外溢后的中间形态。 - -## 3. 重构目标 - -### 3.1 一级目标 - -1. 建立一套共享的 Inspector 字段编辑内核,成为 `number / text / vector` 唯一的交互主链。 -2. 把“编辑会话”收敛成单一真值源,彻底消除多份 `caret / staged text / editing flag` 的并行持有。 -3. 让 `PropertyGrid` 退回到编排层,只负责路由、布局、结果回写,不再承载每种字段的完整交互细节。 -4. 让 `number / text / vector` 退回到字段语义层,只保留 parse / format / clamp / component adapter 等职责。 -5. 以旧版 editor 的 Inspector 行为为准,恢复交互一致性。 - -### 3.2 二级目标 - -1. 删除“为了同步而同步”的附加状态。 -2. 删除基于字段类型的重复点击/双击/拖动状态机。 -3. 删除隐藏在 `Internal` 头文件中的大块共享交互实现。 -4. 保持文件边界收敛,不再继续把一个控件拆成更多零碎文件。 - -## 4. 非目标 - -这次重构明确不做下面这些事: -- 不顺手重写整个 Inspector -- 不改 bool / enum / asset / color 的交互模型,除非它们必须配合新的编辑会话接口 -- 不为了“通用表单框架”去抽象一个过度泛化的 UI 库 -- 不引入额外的分层包装和空壳接口 -- 不再新增大模板 `Internal.h` 来继续堆共享代码 - -## 5. 目标结构 - -### 5.1 核心原则 - -新的结构必须满足三条原则: -- 一个字段在某一时刻只有一个编辑会话真值源 -- 交互内核统一,字段类型只提供语义适配 -- 渲染层纯消费状态,不再自己补齐编辑逻辑 - -### 5.2 建议落地形态 - -引入一组正式的共享核心类型,放在 `Fields` 层内部正式实现,而不是继续散落在各字段实现里。 - -建议新增: -- `UIEditorEditableFieldCore.h` -- `UIEditorEditableFieldCore.cpp` - -核心只负责这些事情: -- 单击聚焦 -- 双击进入文本编辑 -- 左键拖动数值 scrub -- 激活阈值与双击判定 -- 文本缓冲 -- caret 位置 -- caret blink 起点 -- 提交与取消 -- 鼠标释放后结束拖动 -- 字段级 active / hovered / editing 会话切换 - -字段适配层只负责这些事情: -- 当前字段的基础值读取 -- 当前字段的文本格式化 -- 输入文本解析 -- 数值步进与范围收敛 -- vector 组件索引映射 - -### 5.3 PropertyGrid 的目标职责 - -`UIEditorPropertyGridInteraction` 重构后的职责应当只有: -- 为每一帧构建布局 -- 把命中的字段行转成“字段编辑核心输入” -- 调用共享编辑核心 -- 把核心输出回写到对应字段值 -- 维护选择、展开、弹窗等与字段编辑无关的上层状态 - -它不应该继续持有: -- 多套字段类型 interaction state vector -- 一套全局文本编辑状态再加若干字段局部文本编辑状态 -- 针对不同字段类型手工复制的事件处理分支 - -### 5.4 字段控件的目标职责 - -`UIEditorNumberField` / `UIEditorTextField` / `UIEditorVector*Field` 最终都应降为两类职责: -- 布局与绘制 -- 语义适配 - -它们不应该再各自拥有完整的编辑状态机。 - -### 5.5 文件边界约束 - -这次重构后,字段编辑这条链路必须满足下面的文件约束: -- 一个公开头文件对应一个实现 cpp -- 不再新增“一个功能拆成多个零散 cpp”的结构 -- 不再保留 `VectorFieldInteractionInternal.h` 这类承载大量实现逻辑的内部模板头 - -允许保留的形态是: -- 一个正式共享 core 对 -- 每个字段一个公开定义对 -- `PropertyGrid` 一个编排实现对 - -## 6. 具体改造步骤 - -### 阶段 1: 固化参考行为 - -目标: -- 先把要对齐的旧 editor 行为写成明确规则,避免重构过程中再靠记忆实现 - -要固定下来的行为: -- number/vector 单击只聚焦,不直接进入文本编辑 -- number/vector 左键拖动优先是 scrub -- number/vector 双击进入文本编辑 -- text 双击进入文本编辑 -- 进入编辑态的首帧必须可见 caret -- 鼠标释放后必须立即退出拖动 -- vector 多组件字段的交互语义与单数值字段一致,只是多了组件选择 - -完成标准: -- 把这些规则整理成代码内核的输入输出约束,而不是散在各字段注释里 - -### 阶段 2: 建立共享编辑会话模型 - -目标: -- 用一个正式的共享状态结构替换当前分裂状态 - -建议收敛为一个统一会话: -- `activeFieldId` -- `activeComponentIndex` -- `hoveredFieldId` -- `mode` -- `pointerDownPosition` -- `lastClickTimestamp` -- `lastClickPosition` -- `dragArmed` -- `dragActive` -- `dragStartValue` -- `textBuffer` -- `caret` -- `caretBlinkStartNanoseconds` - -这一阶段要做的事: -- 定义共享 state / result / input 结构 -- 明确什么是会话真值源 -- 明确字段视觉状态中哪些字段可以删掉 - -必须删除的重复所有权: -- `UIEditor*FieldState` 里重复保存的编辑真值 -- 各字段 interaction state 里各自维护的 `UITextInputState` -- 各字段 interaction state 里各自维护的 `UIPropertyEditModel` - -### 阶段 3: 提取统一交互内核 - -目标: -- 把点击、双击、拖动、编辑、提交、取消统一放入共享 core - -共享 core 应该提供的固定流程: -1. 解析命中目标 -2. 更新 hover / focus / pressed -3. 识别双击与拖动启动 -4. 根据字段适配器执行 scrub 或进入文本编辑 -5. 处理键盘输入、左右移动 caret、Backspace/Delete -6. 处理 Enter 提交、Escape 取消、失焦提交或取消 -7. 输出统一的字段变更结果 - -这一阶段结束后: -- `number` 不再自带一套点击和拖动状态机 -- `vector` 不再通过模板 internal header 自己跑一套编辑流程 -- `text` 不再单独复制一套 caret 编辑流程 - -### 阶段 4: 重写字段适配层 - -目标: -- 让 `number / text / vector` 只提供语义,不再提供完整交互 - -建议收敛方式: -- `NumberFieldAdapter` -- `TextFieldAdapter` -- `VectorFieldAdapter` - -适配器要暴露的能力: -- `GetDisplayText` -- `BeginEditText` -- `TryCommitText` -- `ApplyScrubDelta` -- `GetComponentCount` -- `ResolveComponentValue` - -这一层不应该知道: -- 双击判定时间 -- 拖动阈值 -- caret 闪烁周期 -- 指针事件状态机 - -### 阶段 5: 收拢 PropertyGrid 状态 - -目标: -- 把 `UIEditorPropertyGridInteractionState` 从“六套字段状态仓库”收成“一个共享编辑会话 + 少量上层状态” - -必须删除的结构: -- `numberFieldInteractionStates` -- `textFieldInteractionStates` -- `vector2FieldInteractionStates` -- `vector3FieldInteractionStates` -- `vector4FieldInteractionStates` - -PropertyGrid 保留的状态只应包括: -- 选择 -- 键盘导航 -- 指针位置 -- 弹窗状态 -- 一个共享 editable field session - -这样做的直接收益: -- 行为不再依赖字段类型分支顺序 -- 同一时间只能有一个字段编辑会话 -- 失焦、提交、取消路径只剩一条主链 - -### 阶段 6: 收拢渲染状态 - -目标: -- 让字段渲染层只读取 core 提供的视图态,不再自己补同步代码 - -需要完成: -- caret 位置只从共享会话映射 -- blink 起点只从共享会话映射 -- `displayText` 规则统一 -- 编辑态背景、选中态边框、hover 态高亮都从共享视图态派生 - -必须避免的终态: -- 渲染层再次缓存一份独立的 caret 或 staged text -- 某字段在渲染前还要再“猜测自己是不是正在编辑” - -### 阶段 7: 删除旧逻辑和临时实现 - -目标: -- 在新主链跑通后,彻底删除旧分支,避免双轨并存 - -必须删除: -- `VectorFieldInteractionInternal.h` -- 各字段交互里重复的双击/拖动/编辑辅助函数 -- `PropertyGridInteraction.cpp` 内基于字段类型复制的同构流程 -- 为了修某个 bug 新增的局部同步字段 - -这一步不能省略,否则之后还会继续从旧逻辑里长出回归 bug。 - -## 7. 验收标准 - -### 7.1 行为验收 - -下面这些行为必须全部稳定: -- number 单击只聚焦 -- number 左键拖动可 scrub,松开立即停止 -- number 双击立即进入编辑态,首帧即可看到 caret -- text 双击进入编辑态,首帧即可看到 caret -- vector 单组件与多组件都遵守同一套交互规则 -- vector 组件拖动与文本编辑互不串状态 -- 失焦、Enter、Escape 的结果一致且可预测 - -### 7.2 结构验收 - -下面这些结构问题必须被消除: -- 不再有多套 `caret` 真值 -- 不再有多套 `editing` 真值 -- `PropertyGrid` 不再持有按字段类型拆开的多套 interaction state vector -- `number / text / vector` 不再分别维护完整状态机 -- 不再存在承载核心交互实现的大型 `Internal.h` - -### 7.3 对标验收 - -至少保证与旧 editor 对齐的点: -- number/vector 的 drag-first, text-edit-second 语义 -- Inspector 字段进入编辑的手感一致 -- 同类字段之间不会再出现“一个能用,一个不能用”的漂移 - -## 8. 实施顺序建议 - -建议按下面的顺序推进,避免继续在旧结构上打补丁: - -1. 先写共享会话模型和 shared core -2. 先迁移 number -3. 再迁移 vector 组件字段 -4. 最后迁移 text -5. 收掉 PropertyGrid 的多套字段状态 -6. 最后删旧逻辑 - -原因很直接: -- number 最简单,最适合作为第一块验证新主链 -- vector 是这次问题最多、重复逻辑最多的部分 -- text 最后迁移,可以直接复用已经稳定的 caret / input / commit 主链 - -## 9. 风险与约束 - -### 9.1 主要风险 - -- 如果新 core 和旧字段状态并存太久,会继续出现双轨同步问题 -- 如果先改渲染不改会话所有权,只会继续加补丁 -- 如果继续保留 per-type interaction state vector,PropertyGrid 仍然无法真正收口 - -### 9.2 必须坚持的约束 - -- 不通过增加更多同步字段来“暂时修好” -- 不通过新增更多 `Internal helper` 来掩盖重复状态机 -- 不把问题继续分散到更多碎文件 -- 不偏离旧 editor 的核心交互语义 - -## 10. 完成标志 - -这份计划完成时,应当达到下面的终态: -- Inspector 可编辑字段只有一套共享交互内核 -- caret、文本缓冲、编辑态、拖动态都有唯一真值源 -- `PropertyGrid` 是编排层,不是字段交互垃圾场 -- `number / text / vector` 是语义适配层,不是三套并行编辑器 -- 后续再改字段行为时,只需要改共享 core 或字段适配器,而不是到处补同步代码 - diff --git a/docs/used/NewEditor_Inspector性能与颜色预览链路重构计划_2026-04-20.md b/docs/used/NewEditor_Inspector性能与颜色预览链路重构计划_2026-04-20.md deleted file mode 100644 index 84f0c1e9..00000000 --- a/docs/used/NewEditor_Inspector性能与颜色预览链路重构计划_2026-04-20.md +++ /dev/null @@ -1,164 +0,0 @@ -# NewEditor Inspector性能与颜色预览链路重构计划 -日期: `2026-04-20` - -## 1. 问题定位 - -这次不是修一个颜色选择器控件 bug,而是重构 `new_editor` Inspector 的错误数据流。 - -当前根因: -- Inspector 没有稳定的展示模型,每帧都把 runtime 全量投影成一份新的 `InspectorPresentationModel` -- 分离颜色选择器的每一次拖动,都直接走 `ApplyChangedField -> SceneRuntime -> BuildInspectorPresentationModel` -- 结果是高频交互被错误绑定到“正式提交 + 全量重建 Inspector”这条重链路上 - -这不是单点实现失误,而是 Inspector 架构边界错了。 - -## 2. 重构目标 - -这次重构必须达到以下目标: - -1. Inspector 从“每帧全量投影页”改成“稳定结构模型” -2. 字段值同步与结构重建彻底分离 -3. 颜色选择器拖动只走“字段值更新 + scene 预览同步”,不再触发整页重建 -4. 只有真正影响字段结构的变更,才允许重建 Inspector -5. Scene 中的外部变更也能推动 Inspector 刷新,但刷新走轻量值同步,不走全量构建 - -## 3. 目标结构 - -### 3.1 Inspector 层 - -Inspector 维护三类状态: - -- `subject` - - 当前查看对象是谁 -- `presentation` - - 稳定的 section / field 结构 -- `sync state` - - 上次同步的 scene revision - - 上次结构签名 - - 结构失效标记 - -更新原则: - -- `subject` 变化: 重建 -- 结构签名变化: 重建 -- scene revision 变化但结构未变: 只同步字段值 -- 颜色选择器 revision 变化: 只更新目标字段并把值同步到 scene,不重建 - -### 3.2 SceneRuntime 层 - -SceneRuntime 提供明确的 Inspector 变更 revision。 - -凡是会影响 Inspector 展示值的场景变更,都必须推进 revision,包括: - -- 组件字段修改 -- Transform 修改 -- Gizmo 预览 -- Undo / Redo -- 组件删除 -- 可能影响 Inspector 结构的场景变更 - -这样 Inspector 不需要再靠“每帧重建”去撞对结果。 - -### 3.3 ComponentEditor 层 - -组件编辑器职责拆成三块: - -1. `BuildSections` - - 构建稳定结构 -2. `SyncFieldValue` - - 把 runtime 当前值回写到已存在字段 -3. `DoesFieldAffectStructure` - - 判断某个字段变更是否会影响当前组件的 Inspector 结构 - -必要时组件编辑器还要能提供结构签名,用于判断是否需要重建。 - -## 4. 实施步骤 - -### 阶段 1: 固化 Inspector 的新契约 - -要做: - -- 扩展 `InspectorPresentationModel`,加入结构签名 -- 新增 presentation 值同步入口 -- 明确 InspectorPanel 的“重建 / 同步 / 预览提交”三条路径 - -完成标准: - -- InspectorPanel 不再每帧直接调用全量构建函数 - -### 阶段 2: 建立 SceneRuntime revision - -要做: - -- 在 `EditorSceneRuntime` 中增加 Inspector revision -- 所有影响 Inspector 可见值的 mutation 路径统一推进 revision - -完成标准: - -- Inspector 可以基于 revision 判断是否需要同步值 - -### 阶段 3: 扩展组件编辑器接口 - -要做: - -- 给 `IInspectorComponentEditor` 增加字段值同步接口 -- 给 `IInspectorComponentEditor` 增加结构影响判断接口 -- 对所有已注册组件编辑器补齐实现 - -重点: - -- Camera -- Light -- AudioSource -- MeshRenderer - -这些组件存在明显的条件字段或动态结构,必须显式处理 - -### 阶段 4: 重构 InspectorPanel 主链 - -要做: - -- 删掉当前“每帧无条件 BuildInspectorPresentationModel”路径 -- 建立 `EnsurePresentationBuilt` -- 建立 `SyncPresentationValues` -- 建立 `InvalidatePresentationStructure` -- 颜色选择器更新改为: - - 更新目标字段本地值 - - 写 scene preview - - 仅在必要时标记结构失效 - -完成标准: - -- 拖动颜色时,不再发生整页 Inspector 重建 - -### 阶段 5: 验证主路径 - -要验证: - -- 颜色选择器连续拖动时 Inspector 与 Scene 都能实时反馈 -- Transform Gizmo 预览时 Inspector 数值同步更新 -- Camera / Light / AudioSource 等条件字段切换后结构正确刷新 -- 普通 number / bool / enum / asset 修改不出现回退或脏状态残留 - -## 5. 非目标 - -这次不做: - -- 重写整个 PropertyGrid -- 改颜色控件的绘制算法 -- 为了解决 Inspector 性能问题去新增另一套临时 Inspector -- 继续用“颜色选择器特殊判断”硬绕过 Inspector 主链 - -## 6. 验收标准 - -这次重构完成后,以下现象必须消失: - -- 拖动颜色时 Inspector 明显卡顿 -- 颜色变化每一帧都触发整页重建 -- Inspector 必须依赖全量 rebuild 才能保持值正确 - -同时必须成立: - -- Inspector 的结构变化与值变化在架构上分离 -- 高频交互走轻量同步路径 -- 重路径只在结构变化时触发 diff --git a/docs/used/NewEditor_Project面板重构计划_2026-04-20.md b/docs/used/NewEditor_Project面板重构计划_2026-04-20.md deleted file mode 100644 index a2cb77d5..00000000 --- a/docs/used/NewEditor_Project面板重构计划_2026-04-20.md +++ /dev/null @@ -1,721 +0,0 @@ -# NewEditor Project 面板重构计划 -日期: `2026-04-20` - -## 1. 文档定位 - -这份计划针对 `new_editor` 当前 `Project` 面板的真实代码状态,覆盖以下范围: - -- `new_editor/app/Features/Project/ProjectPanel.*` -- `new_editor/app/Features/Project/ProjectBrowserModel.*` -- `new_editor/app/Project/EditorProjectRuntime.*` -- 与缩略图/内置图标推进直接相关的调用链 - -这次计划的目标不是“把一个大 cpp 拆成多个 cpp 伪装成重构”,而是: - -- 先把现有结构里的职责边界重新收紧 -- 把明显的副作用和重复状态修复逻辑收口 -- 只在确实独立、可复用、可测试的地方抽离模块 -- 保持每一步都能单独编译、单独验证、单独回退 - -## 2. 基本结论 - -### 2.1 先把话说死 - -`ProjectPanel` 不应该为了“看起来模块化”被拆成一堆 `Renderer`、`Interaction`、`Controller`、`Adapter` cpp 文件。 - -那种做法的主要问题是: - -- 外部类型数量暴涨,但真实依赖关系没有变清晰 -- 状态仍然纠缠,只是从一个文件变成多个文件互相来回传 -- 用户阅读入口变多,维护成本反而上升 -- 很容易把“一个类内部组织不清晰”伪装成“工程结构升级” - -这份新计划的前提是: - -- `ProjectPanel` 继续保持为一个公开类 -- 第一阶段继续保持 `ProjectPanel.h + ProjectPanel.cpp` -- 优先做类内部结构重组,而不是文件爆炸 -- 只有真正独立的逻辑才允许抽出去 - -### 2.2 真正有意义的重构方向 - -真正有价值的方向不是“拆文件”,而是下面几件事: - -- 让 `Append()` 变回纯绘制 -- 让 `Update()` 从“大杂烩过程函数”变成清晰的阶段编排 -- 把重复出现的“操作后 UI 修复逻辑”合并成统一路径 -- 把 `ProjectBrowserModel` 里真正独立的领域逻辑、路径逻辑、文件系统逻辑抽出来 -- 把错误返回从 `bool + catch (...)` 提升为可诊断的结果结构 - -## 3. 当前代码的主要结构问题 - -### 3.1 `ProjectPanel` 是明显的 God Object - -从当前声明和实现看,`ProjectPanel` 同时承担了: - -- 面板可见性和宿主绑定 -- 命令焦点管理 -- tree selection / expansion / interaction -- asset grid selection / double click / hover -- splitter 拖动 -- breadcrumb 命中与导航 -- inline rename 队列、启动、更新、提交 -- context menu 构建、弹出、派发 -- tree drag/drop 与 asset drag/drop -- runtime 选择同步 -- 布局计算 -- 绘制 -- 事件发射 - -这不是“功能多”本身的问题,而是这些职责没有明显的阶段边界,导致: - -- `Update()` 太长,读代码时必须同时追踪多个状态机 -- 任意新增一个交互,都容易误伤 rename / drag / context menu 的边界条件 -- 操作完成后的状态修复逻辑分散在多个分支 -- 某一类行为很难单独验证 - -### 3.2 `Append()` 有副作用,不符合渲染职责 - -当前 `ProjectPanel::Append()` 里直接执行: - -- `m_icons->BeginFrame()` - -这意味着渲染阶段不仅“消费状态”,还在“推进状态”。这会带来几个直接问题: - -- `Append()` 不是纯绘制,语义不干净 -- 调用顺序一旦调整,缩略图推进时机就会变化 -- 未来如果引入多次绘制、录制回放、离屏预览,行为会变得脆弱 - -这属于典型的“为了先跑通功能,把运行时推进塞进了绘制阶段”的临时实现。 - -### 3.3 `Update()` 的流程分层不清晰 - -当前 `Update()` 里串在一起的内容包括: - -- 面板挂载检测 -- 空模型时强制 refresh -- 输入过滤 -- 命令焦点抢占 -- layout 构建 -- context menu 重建 -- tree layout 构建 -- rename 特判 -- tree interaction -- tree drag/drop -- asset drag/drop -- splitter / breadcrumb / grid pointer 处理 - -这里的核心问题不是“行数很多”,而是没有形成稳定的阶段结构。典型症状包括: - -- 中途多次 `m_layout = BuildLayout(...)` -- 中途多次重建 tree layout -- rename 逻辑通过提前 `return` 切断后续流程 -- drag/drop 完成后各自手工做一遍状态修复 - -这会让后续维护者很难回答几个关键问题: - -- 哪些状态属于 frame 初始化 -- 哪些属于输入预处理 -- 哪些属于模型变更后的统一收尾 -- 哪些行为可以安全地提前返回 - -### 3.4 `ProjectBrowserModel` 混了四类完全不同的东西 - -当前 `ProjectBrowserModel.cpp` 里混在一起的内容包括: - -- path / string / itemId 工具函数 -- 文件系统扫描和变更 -- 资源类型识别与 preview 规则 -- UI 投影视图构建 - -这会造成几个结构性问题: - -- 工具函数只能躲在 file-scope,无法被独立复用或测试 -- 模型层和 UI 组件格式强耦合,尤其是 `UIEditorTreeViewItem` -- 文件系统操作只能返回 `bool`,上层没有诊断信息 -- 后续如果想做测试,只能绕过整个模型大对象 - -### 3.5 目录树构建存在重复扫描 - -`RefreshFolderTree()` 当前每个目录至少会经历两类扫描: - -- `HasChildDirectories(folderPath)` 用一次 -- `CollectSortedChildDirectories(folderPath)` 再用一次 - -也就是说,单个目录在构建树节点时,会为了 `forceLeaf` 和子目录列表做重复遍历。 - -在目录数量上来以后,这种实现会持续放大扫描成本,而且没有结构收益。 - -### 3.6 刷新策略偏“全量重扫” - -当前很多操作结束后都会走这一套: - -- `RefreshFolderTree()` -- `EnsureValidCurrentFolder()` -- `RefreshAssetList()` - -包括但不限于: - -- `CreateFolder` -- `CreateMaterial` -- `RenameItem` -- `DeleteItem` -- `MoveItemToFolder` -- `ReparentFolder` -- `MoveFolderToRoot` - -这说明目前的数据更新模型基本还是“改完以后整块重建”。它短期简单,但问题也很明确: - -- 小改动和大改动代价差不多 -- 难以回答某次变更实际影响了哪些视图 -- 后续做增量更新时,没有稳定的变更语义可以接入 - -### 3.7 错误模型过弱 - -当前 `ProjectBrowserModel` 多处直接: - -- `catch (...) { return false; }` - -这会导致: - -- 上层无法区分“名称非法”“目标已存在”“权限失败”“文件被占用”“路径越界” -- UI 无法给出可解释提示 -- 日志也很难追 - -在软件工程上,这种实现只适合非常早期的打通阶段,不适合作为长期结构保留。 - -### 3.8 `ProjectPanel` 里有明显的“临时接法”痕迹 - -当前实现中已经能看到几类典型信号: - -- 大量 file-scope helper 散在 `ProjectPanel.cpp` 顶部 -- `Update()` 内部嵌套局部 callback struct 适配 drag/drop -- 多处手工同步 selection / hovered / clicked / layout / treeFrame -- 多个交互分支各自决定什么时候 `CloseContextMenu()`、`ClearRenameState()`、`SyncCurrentFolderSelection()` - -这些都不是单点 bug,但它们叠在一起,说明这块代码已经过了“继续顺着堆功能”的安全区。 - -## 4. 重构目标 - -### 4.1 一级目标 - -1. 保持 `ProjectPanel` 为一个公开类,但让它重新回到“面板编排者”的职责。 -2. 让 `Append()` 只负责绘制,不再推进缩略图/图标缓存状态。 -3. 让 `Update()` 形成稳定、可阅读的内部阶段。 -4. 让变更后的 UI 修复逻辑走统一路径,而不是分散在各个分支。 -5. 让 `ProjectBrowserModel` 从“全都做”的实现收敛成更清晰的门面。 -6. 为后续增量刷新、错误提示、测试补全留出接口空间。 - -### 4.2 二级目标 - -1. 降低单函数理解成本。 -2. 降低重复代码密度。 -3. 提升可测试性。 -4. 保持现有 UI 行为不回退。 - -## 5. 非目标 - -这次重构明确不做下面这些事: - -- 不把 `ProjectPanel` 机械拆成多个 `cpp` -- 不重写整套资源系统 -- 不引入 Asset Database -- 不在第一阶段引入异步目录扫描 -- 不修改现有 Project 面板交互语义 -- 不为了“未来也许会有”而过度抽象 - -## 6. 目标结构 - -### 6.1 `ProjectPanel` 的目标形态 - -`ProjectPanel` 的目标不是拆文件,而是在同一个类、同一个主实现文件里形成稳定的内部结构: - -- 一组 frame 级准备函数 -- 一组输入/交互阶段函数 -- 一组变更后统一收尾函数 -- 一组纯布局函数 -- 一组纯绘制函数 - -换句话说,最终要做到: - -- 看 `Update()` 时能看出清晰阶段 -- 看 `Append()` 时只看到绘制 -- 看“某次变更后怎么修状态”时有统一入口 - -### 6.2 `ProjectPanel` 内部推荐收敛方式 - -这里的“拆分”只指类内部的责任分层,不是拆出一堆新类型。 - -建议逐步收敛为以下内部阶段: - -1. `PrepareFrameState` - - 清理 frame 级 transient 状态 - - 解析 panel 可见性 - - 处理未挂载 / 无 runtime 的早退路径 - -2. `PrepareModelAndLayout` - - 确保 browser model 已初始化 - - 同步 selection - - 构建 layout - - 构建 tree layout - - 推进当前帧需要的 visual resources - -3. `ProcessRenameFlow` - - 统一处理 queued rename / active rename - - 决定 rename 是否短路其余交互 - -4. `ProcessTreeFlow` - - tree selection - - tree navigation - - tree rename request - - tree drag/drop - -5. `ProcessAssetGridFlow` - - asset selection - - double click open - - asset drag/drop - - background clear selection - -6. `ProcessPanelChromeFlow` - - splitter - - breadcrumb - - context menu pointer routing - -7. `FinalizeFrameState` - - 整理 pointer capture / release 请求 - - 统一处理 mutation 后 layout/treeFrame 修复 - - 整理事件输出 - -最终 `Update()` 应该更像: - -```cpp -void ProjectPanel::Update(...) { - PrepareFrameState(...); - if (!m_visible) { - return; - } - - PrepareModelAndLayout(...); - if (ProcessRenameFlow(...)) { - FinalizeFrameState(...); - return; - } - - ProcessTreeFlow(...); - ProcessAssetGridFlow(...); - ProcessPanelChromeFlow(...); - FinalizeFrameState(...); -} -``` - -重点在“阶段清晰”,不在“类型数量变多”。 - -### 6.3 `ProjectPanel` 内部必须新增的统一收口 - -当前最值得马上收口的不是“命名”,而是下面两类公共修复路径: - -1. 变更后 UI 修复 - -建议新增一条统一入口,负责处理: - -- `CloseContextMenu()` -- `ClearRenameState()` 或按策略保留 -- `SyncCurrentFolderSelection()` -- `SyncAssetSelectionFromRuntime()` -- hover / last click / drag preview 清理 -- 必要时重建 `m_layout` -- 必要时重建 `m_treeFrame.layout` - -不要继续让 tree drop、asset drop、rename、command dispatch、context menu command 各自手工拼一遍。 - -2. 导航后同步 - -建议把“切 folder 后面板要怎么跟着修”收成一条公共路径,避免以下逻辑在多处重复散落: - -- folder selection 对齐 -- asset selection 清理或重同步 -- breadcrumb/layout 更新 -- 事件发射 - -### 6.4 `Append()` 的目标形态 - -`Append()` 最终只应该消费这些输入: - -- 已经准备好的 `Layout` -- tree/grid 当前可视状态 -- rename 当前 frame -- 已经可用的 icon/thumbnail handle - -不应该在这里做: - -- 缩略图缓存推进 -- 资源加载驱动 -- 模型刷新 -- 任意状态修复 - -`Append()` 保持纯绘制后,后续无论是调试录帧、离屏绘制还是多次重绘,都更稳定。 - -### 6.5 `ProjectBrowserModel` 的目标形态 - -`ProjectBrowserModel` 最终应该是一个“浏览数据门面”,而不是“工具函数大仓库 + 文件系统操作器 + UI builder”。 - -建议它最终主要承担: - -- 当前 folder / asset snapshot 的持有 -- folder/item 查找 -- 导航状态维护 -- 组织调用下层独立逻辑 - -而不是继续亲自堆满所有细节实现。 - -## 7. 可以抽离的内容,和不能乱抽的内容 - -### 7.1 可以抽离的,前提是它们真的独立 - -下面这些内容有明确的独立价值,后续可以逐步从 `ProjectBrowserModel.cpp` 中抽离: - -1. `ProjectPathUtils` - -- UTF-8 path 转换 -- 路径分隔符规范化 -- `itemId` 构建 -- project relative path 构建 -- 路径祖先判断 -- moved itemId remap - -这类代码不依赖 UI,也不依赖模型状态,适合作为独立工具层。 - -2. `ProjectAssetRules` - -- `ResolveItemKind` -- `CanOpenItemKind` -- `CanPreviewItem` -- 扩展名与资源类别规则 - -这类规则天然独立,也适合补单元测试。 - -3. `ProjectFileSystemOps` - -- create / rename / delete / move / reparent -- meta sidecar 处理 -- case-aware rename -- 结构化错误返回 - -这类代码跟 UI 没关系,独立后对测试和诊断都更有价值。 - -### 7.2 不该乱抽的内容 - -下面这些内容不应该为了“拆分”而硬抽: - -- 只是 `ProjectPanel` 内部流程的一小段 UI 逻辑 -- 强依赖 `ProjectPanel` 一堆成员状态的局部行为 -- 只是为了让文件变短,但抽出去以后仍然强耦合的逻辑 - -判断标准只有一个: - -抽出去之后,如果它没有更独立、更可测试、更可复用,那就别抽。 - -## 8. 分阶段实施方案 - -## Phase 0. 建基线 - -### 目标 - -在开始整理结构前,先把现有行为和验证口径固定下来,避免重构中出现“代码更好看了,但行为偷偷变了”。 - -### 任务 - -1. 固定最小回归清单: - -- tree 导航 -- breadcrumb 导航 -- grid 单击选择 -- grid 双击打开 -- 右键菜单 -- rename -- tree drag/drop -- asset drag/drop -- folder icon 显示 -- texture thumbnail 显示 - -2. 补最小 smoke 记录方式: - -- 编译验证 -- 打开工程 -- 切换几个目录 -- 查看一张贴图缩略图是否出现 - -3. 为后续要抽离的纯逻辑锁定测试入口: - -- `itemId` 生成 -- moved item remap -- preview 资格判断 -- 名称合法性判断 - -### 验收标准 - -- `XCUIEditorApp` 可编译 -- 有一份可重复执行的 Project 面板 smoke checklist - -## Phase 1. 先把渲染副作用清掉 - -### 目标 - -不改视觉效果,但把 `Append()` 恢复成真正的纯绘制。 - -### 任务 - -1. 把 `m_icons->BeginFrame()` 从 `ProjectPanel::Append()` 挪出去。 -2. 把缩略图/图标运行时推进放到明确的 update/pre-render 阶段。 -3. 保证 `Append()` 只读取结果,不推进状态。 -4. 检查缩略图首帧行为,避免因为推进时机变化导致首帧空白。 - -### 推荐落点 - -- 先仍然放在 `ProjectPanel` 内部 -- 作为 `Update()` 中的显式阶段,例如 `PrepareVisualResources()` -- 暂时不要为了这一步新建多个文件 - -### 风险 - -- 缩略图缓存推进时机变化后,可能出现一帧延迟 - -### 验收标准 - -- `Append()` 内不再出现资源推进调用 -- 图标和缩略图行为与现状一致 - -## Phase 2. 在 `ProjectPanel` 内部重组,而不是拆文件 - -### 目标 - -保留 `ProjectPanel` 现有公开边界,但把 `Update()` 和相关状态流整理成清晰阶段。 - -### 任务 - -1. 给 `Update()` 拆出成组私有 helper,按阶段编排。 -2. 统一 rename 短路路径,避免多个地方各自早退。 -3. 把 tree 和 asset drag/drop 的后处理逻辑收成公共函数。 -4. 把 splitter / breadcrumb / grid pointer 逻辑从主流程里压缩到清晰段落。 -5. 清理顶部 file-scope helper: - -- 纯绘制相关 helper 可以保留在同一 cpp 的匿名命名空间中 -- 纯数学/文本测量 helper 保持局部但命名更清晰 -- 强依赖成员状态的 helper 优先收回类私有方法 - -### 推荐结果 - -这一阶段结束后,`ProjectPanel.cpp` 仍然可能是大文件,但应该变成“结构清晰的大文件”,而不是“所有事情搅在一起的大文件”。 - -### 验收标准 - -- `Update()` 读起来能看出固定阶段 -- drag/drop、rename、导航后的修复不再多处重复 -- 不新增一堆没有独立价值的新类型 - -## Phase 3. 抽真正独立的 `ProjectBrowserModel` 下层逻辑 - -### 目标 - -把能独立测试、独立复用、与 UI 无关的逻辑从 `ProjectBrowserModel.cpp` 中抽出。 - -### 任务 - -1. 先抽 `ProjectPathUtils` - -- `BuildRelativeItemId` -- `BuildRelativeProjectPath` -- `NormalizePathSeparators` -- 路径祖先判断 -- moved id remap - -2. 再抽 `ProjectAssetRules` - -- `ResolveItemKind` -- `CanOpenItemKind` -- `CanPreviewItem` - -3. 最后整理 `ProjectFileSystemOps` - -- folder/file 创建 -- rename -- delete -- move / reparent -- meta sidecar 联动 - -### 这一阶段的关键约束 - -- 只抽纯工具和真正的服务逻辑 -- 不把 UI 投影逻辑拆出去凑文件数 -- `ProjectBrowserModel` 仍然可以先保留为门面,统一调下层 - -### 验收标准 - -- `ProjectBrowserModel.cpp` 明显变薄 -- 被抽离部分可单独测试 -- 行为不变 - -## Phase 4. 升级文件系统错误模型 - -### 目标 - -把“失败就返回 false”升级为“失败有原因,有诊断价值”。 - -### 任务 - -1. 为文件系统操作定义结构化结果,例如: - -```cpp -enum class ProjectFsError { - None = 0, - InvalidName, - SourceMissing, - TargetExists, - PermissionDenied, - PathOutOfScope, - IoFailure -}; - -struct ProjectFsResult { - bool ok = false; - ProjectFsError error = ProjectFsError::None; - std::string itemId = {}; - std::string message = {}; -}; -``` - -2. 消灭 `catch (...) { return false; }` -3. 使用 `std::error_code` 或明确异常转换,把失败原因保留下来 -4. 让上层至少能做: - -- 日志输出 -- 基础 UI 提示 -- 调试时快速定位 - -### 验收标准 - -- 关键文件系统操作不再只返回裸 `bool` -- 常见失败原因能被区分 - -## Phase 5. 优化刷新策略 - -### 目标 - -先把“全量重扫”收敛到更有语义的更新路径,不要求一步到位做真正增量缓存。 - -### 任务 - -1. 先定义变更影响面,而不是直接写死三连刷: - -- tree changed -- current folder changed -- asset list changed -- selection invalidated -- itemId remapped - -2. 把常见操作映射到影响面: - -- 创建文件夹 -- 创建材质 -- rename 文件 -- rename 文件夹 -- 删除 -- asset move -- folder reparent - -3. 基于影响面决定最小刷新范围。 -4. 优先修复 `RefreshFolderTree()` 的重复扫描: - -- 同一次遍历拿到 child folders -- 顺手确定 `forceLeaf` - -### 说明 - -这一阶段不追求立刻做复杂缓存系统,先把“刷新为什么发生”表达清楚。 - -### 验收标准 - -- 变更后的刷新路径有统一语义 -- 目录树重复扫描被消除 - -## Phase 6. 测试与回归补强 - -### 目标 - -把前面整理出的独立逻辑真正纳入测试覆盖。 - -### 建议优先级 - -1. 纯工具测试 - -- itemId/path 构造 -- 路径祖先判断 -- moved id remap -- 名称合法性判断 - -2. 规则测试 - -- 文件类型识别 -- preview 资格判断 - -3. 文件系统操作测试 - -- rename case-aware -- meta sidecar 联动 -- move/reparent 冲突路径 - -4. UI smoke - -- 目录导航 -- 缩略图展示 -- 右键菜单 -- rename -- 拖拽 - -### 验收标准 - -- 重构后的关键逻辑有稳定回归保护 - -## 9. 实施顺序建议 - -推荐严格按下面顺序推进: - -1. `Append()` 去副作用 -2. `ProjectPanel` 内部阶段化整理 -3. 统一 mutation 后 UI 修复路径 -4. `ProjectBrowserModel` 下层独立逻辑抽离 -5. 文件系统错误模型升级 -6. 刷新策略收敛 -7. 测试补强 - -这个顺序的原因很简单: - -- 先做渲染去副作用,风险最可控 -- 再做 `ProjectPanel` 内部收口,能马上降低维护复杂度 -- 再抽独立模块,避免一边抽一边在混乱状态流上反复返工 - -## 10. 重构完成后的判断标准 - -如果这次重构做对了,最终应该看到的是下面这些变化: - -- `ProjectPanel` 仍然是一个类,但内部阶段非常清楚 -- `Append()` 不再推进任何缓存/状态 -- 操作后的状态修复不再分散重复 -- `ProjectBrowserModel` 不再塞满 file-scope 杂项工具函数 -- 文件系统失败能知道为什么失败 -- 目录树构建不再对同一目录重复扫描 -- 代码结构是真的更清晰,而不是文件数量更多 - -## 11. 最后强调一次 - -这份计划的核心不是“把一个大文件拆成很多文件”。 - -这份计划真正追求的是: - -- 状态流清晰 -- 副作用位置正确 -- 变更后修复路径统一 -- 独立逻辑独立出去 -- 不独立的逻辑就老老实实留在原类里收口 - -只有这样,才叫对代码结构有帮助。 diff --git a/docs/used/NewEditor_严重问题最终收口计划_2026-04-20.md b/docs/used/NewEditor_严重问题最终收口计划_2026-04-20.md deleted file mode 100644 index 50a42520..00000000 --- a/docs/used/NewEditor_严重问题最终收口计划_2026-04-20.md +++ /dev/null @@ -1,167 +0,0 @@ -# NewEditor 严重问题最终收口计划 2026-04-20 - -## 说明 - -- 本轮开始前,`docs/plan` 中已经没有处于活动状态的 `new_editor` 计划;旧 `new_editor` 计划已在此前各轮归档到 `docs/used/`。 -- 本计划只覆盖当前静态审查中仍然属于根因级别的严重问题,不再重复已经完成归档的历史阶段任务。 -- 本计划的目标不是继续打补丁,而是把 `new_editor` 的模块 ownership、层间边界和主干交互契约真正收口。 - -## 当前仍然存在的严重问题 - -### 1. Scene 子系统 ownership 未闭合 - -- `new_editor` 的 scene 资源路径仍然直接指向旧 `editor/resources`。 -- `new_editor` 的 scene tool overlay 图标仍然从旧 `editor/resources/Icons` 读取。 -- `new_editor` 的 scene pass 仍然直接依赖 `engine/include/XCEngine/Rendering/Internal/*`。 - -这说明 `new_editor` 的 scene 子系统仍然处于半迁移状态: - -- 资源 ownership 不在 `new_editor` -- 渲染 helper 契约不在稳定公共层 -- 旧 editor 仍然是 `new_editor` 的隐式上游 - -### 2. 跨窗口 Tab Drag 的平台层边界反向耦合 - -- `WindowManager` 仍然直接读取 shell frame / dock layout 来推导 tab drag hotspot、drop preview 和 drop target。 -- 平台宿主层没有拿到稳定的“拖拽语义接口”,只能消费 UI 帧内部布局结果。 - -这会继续放大以下风险: - -- tab 拖拽与窗口拖动语义互相污染 -- cross-window merge / detach 行为难以稳定 -- UI 层实现细节一变,宿主层就连带失效 - -### 3. XCUIEditorLib 核心公共层仍有结构碎裂 - -- `UIEditorWorkspaceController` 仍然是一个公开类拆到多个 `.cpp`。 -- `Workspace` / `Shell` / `Fields` 仍保留多组 `*Internal.h + 多cpp` 的组织方式。 - -这不是简单的文件多少问题,而是公共层职责没有真正收束: - -- 一个类型的行为被按“主题”而不是按“类型”拆散 -- 私有算法和公共实现边界混杂 -- 后续维护 workspace / shell / property grid 时仍然会重复出现定位困难和改动扩散 - -### 4. Scene Viewport 与旧 Editor 仍是双份演进风险 - -- `new_editor` 的 gizmo / overlay / pass 体系已经形成自己的一份实现。 -- 旧 `editor` 中同时还保留同类 viewport/gizmo/pass 体系。 - -如果不收口 ownership 和共享边界,后果是: - -- 同一领域逻辑在两套实现中分叉演进 -- bug 修复与功能完善无法共享 -- `new_editor` 长期停留在“复制旧 editor 的一份分支”状态 - -## 执行策略 - -### Phase 1. 收口 Scene ownership - -1. 把 `new_editor` 直接依赖旧 `editor/resources` 的路径全部切断。 -2. 把 `scene` 所需资源迁入 `new_editor/resources` 的正式目录,或改为嵌入资源,不再通过旧 editor 路径加载。 -3. 审查 `SceneViewportGridPass` / `SceneViewportSelectionOutlinePass` 依赖的 `Rendering::Internal` helper。 -4. 对真正属于引擎稳定基础设施的 helper,提升到明确公共边界;不再让 `new_editor` 直接包含 internal 头。 -5. 保证最终路径关系为: - - `new_editor -> XCEditor public` - - `new_editor -> engine public` - - 不允许 `new_editor -> editor/resources` - - 不允许 `new_editor -> engine internal` - -### Phase 2. 重构跨窗口 Tab Drag 边界 - -1. 识别宿主层真正需要的拖拽语义数据: - - 是否允许开始跨窗口拖拽 - - 拖拽来源 panel / node - - 鼠标热点 - - 命中的 drop target - - preview 更新请求 -2. 在 UI/editor 公共层建立稳定的 drag transfer 语义接口。 -3. 让 `WindowManager` 消费语义结果,而不是直接窥探 shell frame 内部布局。 -4. 清理 Win32 层对 shell frame / dock layout 细节的反向依赖。 - -### Phase 3. 收口 XCUIEditorLib 结构碎裂 - -1. 以 `一个公开头对应一个实现单元` 为基本规则重新检查 `new_editor/src`。 -2. 优先收口核心模块: - - `Workspace` - - `Shell` - - `Fields` -3. 如果确实存在独立职责,拆成真正独立类型和独立 `.h/.cpp`,而不是继续用 `Internal + 多cpp` 组织一个类型。 -4. 最终目标: - - 公共类型边界稳定 - - 私有算法边界可定位 - - 文件组织与类型组织一致 - -### Phase 4. 评估并收口 Viewport 双份实现风险 - -1. 对比 `editor` 与 `new_editor` 的 viewport / gizmo / pass 责任划分。 -2. 判定哪些逻辑应成为: - - `engine` 公共基础设施 - - `XCEditor` 公共编辑器层 - - `new_editor app` 产品装配层 -3. 不再允许 `new_editor` 长期维持一份只靠复制旧 editor 演化的 scene 子系统。 - -## 本轮执行顺序 - -1. 先完成 Phase 1。 -2. Phase 1 收口并验证后,再进入 Phase 2。 -3. Phase 2 完成后,再清理 Phase 3。 -4. Phase 4 作为最终架构收口,不提前跳过前面三项。 - -## 本轮验收标准 - -- `new_editor` 不再引用旧 `editor/resources`。 -- `new_editor` 的 scene 渲染代码不再包含 `engine/include/XCEngine/Rendering/Internal/*`。 -- `new_editor` 的跨窗口 tab drag 不再依赖 shell frame 内部布局实现细节。 -- `XCUIEditorLib` 核心公共层不再保留明显的“一个公开类型拆多个 cpp”遗留。 -- `XCUIEditorApp` 能重新编译通过,相关 smoke 至少回归一轮。 - -## 当前进展 2026-04-20 - -### 已完成 - -- Phase 1 已完成。 - - `new_editor` 的 scene 资源路径已切回 `new_editor/resources`。 - - scene tool overlay 图标已迁入 `new_editor/resources/Icons`。 - - infinite grid shader 已迁入 `new_editor/resources/shaders/scene-viewport/infinite-grid/`。 - - `SceneViewportGridPass` / `SceneViewportSelectionOutlinePass` 已改为依赖 engine 公共渲染 helper 头,不再直接包含 `Rendering/Internal/*`。 - - engine 已新增正式公共头: - - `engine/include/XCEngine/Rendering/RenderSurfacePipelineUtils.h` - - `engine/include/XCEngine/Rendering/ShaderVariantUtils.h` - - `xcui_editor_app_smoke` 已通过。 - -- Phase 2 第一轮已完成。 - - 已新增 `XCEditor` 公共 dock transfer helper。 - - Win32 `WindowManager` 已不再直接读取 `tabHeaderRects`、`dockHostFrame.layout`、`TabDragDropTarget` 等布局细节来解析 tab drag hotspot / drop target。 - - app 侧临时 `TabDragDropTarget` 文件已删除。 - -- Phase 3 第一轮已完成。 - - `UIEditorWorkspaceController` 已从三个 cpp 合并回单一实现单元。 - - 当前自动核对结果下,`new_editor/include/XCEditor` 中未再发现“一个公开类拆多个 cpp”的直接遗留。 - -### 当前阻塞 - -- `XCUIEditorAppLib` 已成功编译。 -- `XCUIEditorApp` 最终 exe 重新链接时被仓库内无关变更阻塞,当前错误为: - - `ManagedScriptableRenderPipelineAsset::ConfigureCameraFramePlan` 未解析外部符号 -- 这个链接错误来自当前仓库其他改动带入的 engine/managed 主线,不是本轮 `new_editor` 改动引入的新编译错误。 - -### 下一步 - -1. 继续收 Phase 3 剩余碎裂点,重点是 `WorkspaceModel` / `Shell` / `Fields` 的 `Internal + 多cpp` 组织残留。 -2. 在不回退他人改动的前提下,等待或绕过当前 exe 链接阻塞,再补最终整体验证。 - -### չ 2026-04-20 -- Phase 3 ѽһտɣ - - `UIEditorWorkspaceModel` ѲصһʵֵԪ`WorkspaceModelMutation.cpp` / `WorkspaceModelQueries.cpp` / `WorkspaceModelValidation.cpp` ɾ - - `UIEditorShellInteraction` ѲصһʵֵԪ`ShellInteractionRequest.cpp` / `ShellInteractionRendering.cpp` ɾ - - `UIEditorPropertyGrid` ѲصһʵֵԪ`PropertyGridRendering.cpp` ɾ - - `UIEditorPropertyGridInteraction` ѲصһʵֵԪ`PropertyGridInteractionAsset.cpp` / `PropertyGridInteractionColor.cpp` / `PropertyGridInteractionEdit.cpp` / `PropertyGridInteractionHelpers.cpp` / `PropertyGridInteractionPopup.cpp` / `PropertyGridInteractionVector.cpp` ɾ - - `new_editor/CMakeLists.txt` ͬƳƬԴļ -- ǰ֤ - - `cmake --build build --config Debug --target XCUIEditorAppLib` ͨ - - `cmake --build build --config Debug --target XCUIEditorApp` ͨ`XCUIEditor.exe` ɡ - - `ctest --test-dir build -C Debug -R xcui_editor_app_smoke --output-on-failure` ͨ -- ǰע㣺 - - ߲ʣɨ裬ȷǷ񻹴µĽṹ⡣ - - ˶ԵǰƻǷ鵵 diff --git a/new_editor/CMakeLists.txt b/new_editor/CMakeLists.txt index f1cf4f5b..e8673e72 100644 --- a/new_editor/CMakeLists.txt +++ b/new_editor/CMakeLists.txt @@ -168,7 +168,6 @@ set(XCUI_EDITOR_HOST_PLATFORM_SOURCES set(XCUI_EDITOR_HOST_RENDERING_SOURCES app/Rendering/Native/AutoScreenshot.cpp app/Rendering/D3D12/D3D12HostDevice.cpp - app/Rendering/D3D12/D3D12UIRenderer.cpp app/Rendering/D3D12/D3D12ShaderResourceDescriptorAllocator.cpp app/Rendering/D3D12/D3D12WindowInteropContext.cpp app/Rendering/D3D12/D3D12WindowRenderer.cpp diff --git a/new_editor/app/Bootstrap/Application.cpp b/new_editor/app/Bootstrap/Application.cpp index a7a2ef70..46a544a1 100644 --- a/new_editor/app/Bootstrap/Application.cpp +++ b/new_editor/app/Bootstrap/Application.cpp @@ -233,11 +233,7 @@ int Application::Run(HINSTANCE hInstance, int nCmdShow) { break; } - if (m_windowManager->RenderScheduledWindows()) { - continue; - } - - WaitMessage(); + m_windowManager->RenderAllWindows(); } else { break; } diff --git a/new_editor/app/Features/Project/ProjectPanel.cpp b/new_editor/app/Features/Project/ProjectPanel.cpp index 6112f460..8c3bc863 100644 --- a/new_editor/app/Features/Project/ProjectPanel.cpp +++ b/new_editor/app/Features/Project/ProjectPanel.cpp @@ -41,8 +41,8 @@ inline constexpr float kBreadcrumbItemPaddingX = 4.0f; inline constexpr float kBreadcrumbItemPaddingY = 1.0f; inline constexpr float kBreadcrumbSpacing = 3.0f; inline constexpr float kTreeTopPadding = 0.0f; -inline constexpr float kGridInsetX = 0.0f; -inline constexpr float kGridInsetY = 0.0f; +inline constexpr float kGridInsetX = 16.0f; +inline constexpr float kGridInsetY = 12.0f; inline constexpr float kGridTileWidth = 92.0f; inline constexpr float kGridTileHeight = 92.0f; inline constexpr float kGridTileGapX = 12.0f; @@ -250,87 +250,6 @@ bool HasValidBounds(const UIRect& bounds) { constexpr auto kGridDoubleClickInterval = std::chrono::milliseconds(400); -Widgets::UIEditorScrollViewMetrics BuildProjectGridScrollMetrics() { - Widgets::UIEditorScrollViewMetrics metrics = ResolveUIEditorScrollViewMetrics(); - metrics.scrollbarWidth = 8.0f; - metrics.scrollbarInset = 3.0f; - metrics.minThumbHeight = 28.0f; - metrics.cornerRounding = 0.0f; - metrics.borderThickness = 0.0f; - metrics.focusedBorderThickness = 0.0f; - return metrics; -} - -Widgets::UIEditorScrollViewPalette BuildProjectGridScrollPalette() { - Widgets::UIEditorScrollViewPalette palette = ResolveUIEditorScrollViewPalette(); - palette.surfaceColor = kPaneColor; - return palette; -} - -int ResolveProjectGridColumnCount(float gridWidth) { - const float effectiveTileWidth = kGridTileWidth + kGridTileGapX; - int columnCount = effectiveTileWidth > 0.0f - ? static_cast((ClampNonNegative(gridWidth) + kGridTileGapX) / effectiveTileWidth) - : 1; - if (columnCount < 1) { - columnCount = 1; - } - return columnCount; -} - -float MeasureProjectGridContentHeight( - std::size_t itemCount, - int columnCount) { - if (itemCount == 0u || columnCount < 1) { - return 0.0f; - } - - const std::size_t resolvedColumnCount = static_cast(columnCount); - const std::size_t rowCount = - (itemCount + resolvedColumnCount - 1u) / resolvedColumnCount; - return static_cast(rowCount) * kGridTileHeight + - static_cast((std::max)(rowCount, std::size_t(1u)) - 1u) * - kGridTileGapY; -} - -Widgets::UIEditorScrollViewLayout BuildProjectGridScrollLayout( - const UIRect& bounds, - std::size_t itemCount, - float verticalOffset, - const Widgets::UIEditorScrollViewMetrics& metrics, - int* resolvedColumnCount = nullptr) { - int columnCount = ResolveProjectGridColumnCount(bounds.width); - float contentHeight = MeasureProjectGridContentHeight(itemCount, columnCount); - - Widgets::UIEditorScrollViewLayout scrollLayout = {}; - for (int iteration = 0; iteration < 3; ++iteration) { - scrollLayout = - Widgets::BuildUIEditorScrollViewLayout(bounds, contentHeight, verticalOffset, metrics); - verticalOffset = scrollLayout.verticalOffset; - - const int nextColumnCount = - ResolveProjectGridColumnCount(scrollLayout.contentRect.width); - const float nextContentHeight = - MeasureProjectGridContentHeight(itemCount, nextColumnCount); - if (nextColumnCount == columnCount && - std::abs(nextContentHeight - contentHeight) <= 0.01f) { - columnCount = nextColumnCount; - contentHeight = nextContentHeight; - break; - } - - columnCount = nextColumnCount; - contentHeight = nextContentHeight; - } - - scrollLayout = - Widgets::BuildUIEditorScrollViewLayout(bounds, contentHeight, verticalOffset, metrics); - if (resolvedColumnCount != nullptr) { - *resolvedColumnCount = columnCount; - } - return scrollLayout; -} - Widgets::UIEditorMenuPopupItem BuildContextMenuCommandItem( std::string itemId, std::string label, @@ -431,14 +350,12 @@ void ProjectPanel::SetTextMeasurer(const UIEditorTextMeasurer* textMeasurer) { void ProjectPanel::ResetInteractionState() { m_assetDragState = {}; m_treeDragState = {}; - m_gridScrollInteractionState = {}; m_treeInteractionState = {}; m_treeFrame = {}; m_contextMenu = {}; ClearRenameState(); m_frameEvents.clear(); m_layout = {}; - m_gridVerticalOffset = 0.0f; m_hoveredAssetItemId.clear(); m_lastPrimaryClickedAssetId.clear(); m_lastPrimaryClickTime = {}; @@ -470,7 +387,6 @@ bool ProjectPanel::WantsHostPointerRelease() const { bool ProjectPanel::HasActivePointerCapture() const { return m_splitterDragging || - m_gridScrollInteractionState.scrollViewState.draggingScrollbarThumb || GridDrag::HasActivePointerCapture(m_assetDragState) || TreeDrag::HasActivePointerCapture(m_treeDragState); } @@ -624,11 +540,6 @@ bool ProjectPanel::TryStartQueuedRenameSession() { initialText = folder->label; } - if (m_pendingRenameSurface == RenameSurface::Grid && - HasValidBounds(m_layout.bounds)) { - EnsureAssetVisible(m_pendingRenameItemId, m_layout.bounds); - } - const UIRect bounds = BuildRenameBounds(m_pendingRenameItemId, m_pendingRenameSurface); if (!HasValidBounds(bounds)) { @@ -798,10 +709,6 @@ bool ProjectPanel::NavigateToFolder(std::string_view itemId, EventSource source) SyncCurrentFolderSelection(); SyncAssetSelectionFromRuntime(); - ResetGridScrollPosition(); - if (HasValidBounds(m_layout.bounds)) { - RebuildLayout(m_layout.bounds); - } m_hoveredAssetItemId.clear(); m_lastPrimaryClickedAssetId.clear(); EmitEvent( @@ -822,8 +729,7 @@ bool ProjectPanel::OpenProjectItem(std::string_view itemId, EventSource source) if (navigated && HasValidBounds(m_layout.bounds)) { SyncCurrentFolderSelection(); SyncAssetSelectionFromRuntime(); - ResetGridScrollPosition(); - RebuildLayout(m_layout.bounds); + m_layout = BuildLayout(m_layout.bounds); m_hoveredAssetItemId.clear(); EmitEvent( EventKind::FolderNavigated, @@ -1293,9 +1199,6 @@ UIEditorHostCommandDispatchResult ProjectPanel::DispatchAssetCommand( m_lastPrimaryClickTime = {}; ResolveProjectRuntime()->SetSelection(createdItemId); SyncAssetSelectionFromRuntime(); - if (m_visible && HasValidBounds(m_layout.bounds)) { - EnsureAssetVisible(createdItemId, m_layout.bounds); - } const AssetEntry* createdAsset = FindAssetEntry(createdItemId); if (createdAsset == nullptr) { @@ -1327,7 +1230,7 @@ UIEditorHostCommandDispatchResult ProjectPanel::DispatchAssetCommand( if (target.containerFolder->itemId != GetBrowserModel().GetCurrentFolderId()) { NavigateToFolder(target.containerFolder->itemId, EventSource::GridSecondary); if (HasValidBounds(m_layout.bounds)) { - RebuildLayout(m_layout.bounds); + m_layout = BuildLayout(m_layout.bounds); } } @@ -1359,7 +1262,7 @@ UIEditorHostCommandDispatchResult ProjectPanel::DispatchAssetCommand( if (target.containerFolder->itemId != GetBrowserModel().GetCurrentFolderId()) { NavigateToFolder(target.containerFolder->itemId, EventSource::GridSecondary); if (HasValidBounds(m_layout.bounds)) { - RebuildLayout(m_layout.bounds); + m_layout = BuildLayout(m_layout.bounds); } } @@ -1586,7 +1489,6 @@ void ProjectPanel::Update( FindMountedProjectPanel(contentHostFrame); if (panelState == nullptr) { if (m_splitterDragging || - m_gridScrollInteractionState.scrollViewState.draggingScrollbarThumb || m_assetDragState.dragging || m_treeDragState.dragging || m_renameState.active) { @@ -1595,7 +1497,6 @@ void ProjectPanel::Update( m_visible = false; m_assetDragState = {}; m_treeDragState = {}; - m_gridScrollInteractionState = {}; CloseContextMenu(); ClearRenameState(); ResetTransientFrames(); @@ -1603,11 +1504,7 @@ void ProjectPanel::Update( } if (!HasProjectRuntime()) { - if (m_gridScrollInteractionState.scrollViewState.draggingScrollbarThumb) { - m_requestPointerRelease = true; - } m_visible = false; - m_gridScrollInteractionState = {}; CloseContextMenu(); ClearRenameState(); ResetTransientFrames(); @@ -1643,7 +1540,7 @@ void ProjectPanel::Update( ClaimCommandFocus(filteredEvents, panelState->bounds, inputContext.allowInteraction); m_navigationWidth = ClampNavigationWidth(m_navigationWidth, panelState->bounds.width); - RebuildLayout(panelState->bounds); + m_layout = BuildLayout(panelState->bounds); if (m_contextMenu.open) { RebuildContextMenu(); } @@ -1687,7 +1584,7 @@ void ProjectPanel::Update( m_treeFrame.result.selectedItemId != GetBrowserModel().GetCurrentFolderId()) { CloseContextMenu(); NavigateToFolder(m_treeFrame.result.selectedItemId, EventSource::Tree); - RebuildLayout(panelState->bounds); + m_layout = BuildLayout(panelState->bounds); } if (m_treeFrame.result.renameRequested && !m_treeFrame.result.renameItemId.empty()) { @@ -1773,7 +1670,7 @@ void ProjectPanel::Update( EmitSelectionClearedEvent(EventSource::Tree); } SyncCurrentFolderSelection(); - RebuildLayout(panelState->bounds); + m_layout = BuildLayout(panelState->bounds); m_treeFrame.layout = Widgets::BuildUIEditorTreeViewLayout( m_layout.treeRect, GetBrowserModel().GetTreeItems(), @@ -1782,27 +1679,6 @@ void ProjectPanel::Update( m_treeInteractionState.verticalOffset); } - const Widgets::UIEditorScrollViewMetrics gridScrollMetrics = - BuildProjectGridScrollMetrics(); - const bool hadGridScrollCapture = - m_gridScrollInteractionState.scrollViewState.draggingScrollbarThumb; - UpdateUIEditorScrollViewInteraction( - m_gridScrollInteractionState, - m_gridVerticalOffset, - m_layout.gridRect, - m_layout.gridScrollLayout.contentHeight, - filteredEvents, - gridScrollMetrics); - if (!hadGridScrollCapture && - m_gridScrollInteractionState.scrollViewState.draggingScrollbarThumb) { - m_requestPointerCapture = true; - } else if ( - hadGridScrollCapture && - !m_gridScrollInteractionState.scrollViewState.draggingScrollbarThumb) { - m_requestPointerRelease = true; - } - RebuildLayout(panelState->bounds); - struct ProjectAssetDragCallbacks { ::XCEngine::UI::Widgets::UISelectionModel& assetSelection; ::XCEngine::UI::Widgets::UIExpansionModel& folderExpansion; @@ -1822,10 +1698,6 @@ void ProjectPanel::Update( } std::string ResolveDraggableItem(const UIPoint& point) const { - if (!ContainsPoint(layout.gridScrollLayout.contentRect, point)) { - return {}; - } - for (const AssetTileLayout& tile : layout.assetTiles) { if (tile.itemIndex >= assetEntries.size()) { continue; @@ -1921,7 +1793,7 @@ void ProjectPanel::Update( } } - RebuildLayout(panelState->bounds); + m_layout = BuildLayout(panelState->bounds); m_treeFrame.layout = Widgets::BuildUIEditorTreeViewLayout( m_layout.treeRect, GetBrowserModel().GetTreeItems(), @@ -1973,7 +1845,7 @@ void ProjectPanel::Update( if (m_splitterDragging) { m_navigationWidth = ClampNavigationWidth(event.position.x - panelState->bounds.x, panelState->bounds.width); - RebuildLayout(panelState->bounds); + m_layout = BuildLayout(panelState->bounds); } m_splitterHovered = @@ -2008,7 +1880,7 @@ void ProjectPanel::Update( m_pressedBreadcrumbIndex = HitTestBreadcrumbItem(event.position); - if (!ContainsPoint(m_layout.gridScrollLayout.contentRect, event.position)) { + if (!ContainsPoint(m_layout.gridRect, event.position)) { break; } @@ -2049,7 +1921,7 @@ void ProjectPanel::Update( } if (event.pointerButton == ::XCEngine::UI::UIPointerButton::Right && - ContainsPoint(m_layout.gridScrollLayout.contentRect, event.position)) { + ContainsPoint(m_layout.gridRect, event.position)) { const auto& assetEntries = GetBrowserModel().GetAssetEntries(); const std::size_t hitIndex = HitTestAssetTile(event.position); if (hitIndex >= assetEntries.size()) { @@ -2094,7 +1966,7 @@ void ProjectPanel::Update( m_layout.breadcrumbItems[releasedBreadcrumbIndex]; if (item.clickable) { NavigateToFolder(item.targetFolderId, EventSource::Breadcrumb); - RebuildLayout(panelState->bounds); + m_layout = BuildLayout(panelState->bounds); } } m_pressedBreadcrumbIndex = kInvalidLayoutIndex; @@ -2157,15 +2029,6 @@ ProjectPanel::Layout ProjectPanel::BuildLayout(const UIRect& bounds) const { layout.browserBodyRect.y + kGridInsetY, ClampNonNegative(layout.browserBodyRect.width - kGridInsetX * 2.0f), ClampNonNegative(layout.browserBodyRect.height - kGridInsetY * 2.0f)); - const Widgets::UIEditorScrollViewMetrics gridScrollMetrics = - BuildProjectGridScrollMetrics(); - int columnCount = 1; - layout.gridScrollLayout = BuildProjectGridScrollLayout( - layout.gridRect, - assetEntries.size(), - m_gridVerticalOffset, - gridScrollMetrics, - &columnCount); const float breadcrumbRowHeight = kHeaderFontSize + kBreadcrumbItemPaddingY * 2.0f; const float breadcrumbY = @@ -2216,16 +2079,20 @@ ProjectPanel::Layout ProjectPanel::BuildLayout(const UIRect& bounds) const { nextItemX += itemWidth + kBreadcrumbSpacing; } - const UIPoint gridContentOrigin = - Widgets::ResolveUIEditorScrollViewContentOrigin(layout.gridScrollLayout); + const float effectiveTileWidth = kGridTileWidth + kGridTileGapX; + int columnCount = effectiveTileWidth > 0.0f + ? static_cast((layout.gridRect.width + kGridTileGapX) / effectiveTileWidth) + : 1; + if (columnCount < 1) { + columnCount = 1; + } + layout.assetTiles.reserve(assetEntries.size()); for (std::size_t index = 0; index < assetEntries.size(); ++index) { const int column = static_cast(index % static_cast(columnCount)); const int row = static_cast(index / static_cast(columnCount)); - const float tileX = - gridContentOrigin.x + static_cast(column) * (kGridTileWidth + kGridTileGapX); - const float tileY = - gridContentOrigin.y + static_cast(row) * (kGridTileHeight + kGridTileGapY); + const float tileX = layout.gridRect.x + static_cast(column) * (kGridTileWidth + kGridTileGapX); + const float tileY = layout.gridRect.y + static_cast(row) * (kGridTileHeight + kGridTileGapY); AssetTileLayout tile = {}; tile.itemIndex = index; @@ -2246,59 +2113,6 @@ ProjectPanel::Layout ProjectPanel::BuildLayout(const UIRect& bounds) const { return layout; } -void ProjectPanel::RebuildLayout(const UIRect& bounds) { - m_layout = BuildLayout(bounds); - m_gridVerticalOffset = m_layout.gridScrollLayout.verticalOffset; -} - -void ProjectPanel::ResetGridScrollPosition() { - m_gridVerticalOffset = 0.0f; - m_gridScrollInteractionState.scrollViewState.draggingScrollbarThumb = false; -} - -void ProjectPanel::EnsureAssetVisible( - std::string_view itemId, - const UIRect& bounds) { - if (itemId.empty() || !HasValidBounds(bounds)) { - return; - } - - RebuildLayout(bounds); - const Widgets::UIEditorScrollViewMetrics gridScrollMetrics = - BuildProjectGridScrollMetrics(); - const auto& assetEntries = GetBrowserModel().GetAssetEntries(); - for (const AssetTileLayout& tile : m_layout.assetTiles) { - if (tile.itemIndex >= assetEntries.size()) { - continue; - } - - if (assetEntries[tile.itemIndex].itemId != itemId) { - continue; - } - - const UIRect& contentRect = m_layout.gridScrollLayout.contentRect; - float adjustedOffset = m_gridVerticalOffset; - const float tileBottom = tile.tileRect.y + tile.tileRect.height; - const float contentBottom = contentRect.y + contentRect.height; - if (tile.tileRect.y < contentRect.y) { - adjustedOffset -= contentRect.y - tile.tileRect.y; - } else if (tileBottom > contentBottom) { - adjustedOffset += tileBottom - contentBottom; - } - - adjustedOffset = Widgets::ClampUIEditorScrollViewOffset( - m_layout.gridScrollLayout.bounds, - m_layout.gridScrollLayout.contentHeight, - adjustedOffset, - gridScrollMetrics); - if (std::abs(adjustedOffset - m_gridVerticalOffset) > 0.01f) { - m_gridVerticalOffset = adjustedOffset; - RebuildLayout(bounds); - } - return; - } -} - std::size_t ProjectPanel::HitTestBreadcrumbItem(const UIPoint& point) const { for (std::size_t index = 0u; index < m_layout.breadcrumbItems.size(); ++index) { const BreadcrumbItemLayout& item = m_layout.breadcrumbItems[index]; @@ -2311,10 +2125,6 @@ std::size_t ProjectPanel::HitTestBreadcrumbItem(const UIPoint& point) const { } std::size_t ProjectPanel::HitTestAssetTile(const UIPoint& point) const { - if (!ContainsPoint(m_layout.gridScrollLayout.contentRect, point)) { - return kInvalidLayoutIndex; - } - for (const AssetTileLayout& tile : m_layout.assetTiles) { if (ContainsPoint(tile.tileRect, point)) { return tile.itemIndex; @@ -2385,16 +2195,6 @@ void ProjectPanel::Append(UIDrawList& drawList) const { m_layout.browserHeaderRect.width, kHeaderBottomBorderThickness), ResolveUIEditorDockHostPalette().splitterColor); - const Widgets::UIEditorScrollViewPalette gridScrollPalette = - BuildProjectGridScrollPalette(); - const Widgets::UIEditorScrollViewMetrics gridScrollMetrics = - BuildProjectGridScrollMetrics(); - AppendUIEditorScrollViewBackground( - drawList, - m_layout.gridScrollLayout, - m_gridScrollInteractionState.scrollViewState, - gridScrollPalette, - gridScrollMetrics); const Widgets::UIEditorTreeViewPalette treePalette = ResolveUIEditorTreeViewPalette(); const Widgets::UIEditorTreeViewMetrics treeMetrics = ResolveUIEditorTreeViewMetrics(); @@ -2474,7 +2274,6 @@ void ProjectPanel::Append(UIDrawList& drawList) const { } drawList.PopClipRect(); - drawList.PushClipRect(m_layout.gridScrollLayout.contentRect); for (const AssetTileLayout& tile : m_layout.assetTiles) { if (tile.itemIndex >= assetEntries.size()) { continue; @@ -2540,7 +2339,6 @@ void ProjectPanel::Append(UIDrawList& drawList) const { break; } } - drawList.PopClipRect(); if (m_renameState.active) { const Widgets::UIEditorTextFieldPalette textFieldPalette = @@ -2553,30 +2351,19 @@ void ProjectPanel::Append(UIDrawList& drawList) const { BuildUIEditorPropertyGridTextFieldMetrics( ResolveUIEditorPropertyGridMetrics(), ResolveUIEditorTextFieldMetrics())); - if (m_activeRenameSurface == RenameSurface::Grid) { - drawList.PushClipRect(m_layout.gridScrollLayout.contentRect); - AppendUIEditorInlineRenameSession( - drawList, - m_renameFrame, - m_renameState, - textFieldPalette, - textFieldMetrics); - drawList.PopClipRect(); - } else { - AppendUIEditorInlineRenameSession( - drawList, - m_renameFrame, - m_renameState, - textFieldPalette, - textFieldMetrics); - } + AppendUIEditorInlineRenameSession( + drawList, + m_renameFrame, + m_renameState, + textFieldPalette, + textFieldMetrics); } if (assetEntries.empty()) { const UIRect messageRect( - m_layout.gridScrollLayout.contentRect.x, - m_layout.gridScrollLayout.contentRect.y, - m_layout.gridScrollLayout.contentRect.width, + m_layout.gridRect.x, + m_layout.gridRect.y, + m_layout.gridRect.width, 18.0f); drawList.AddText( UIPoint(messageRect.x, ResolveTextTop(messageRect.y, messageRect.height, kHeaderFontSize)), diff --git a/new_editor/app/Features/Project/ProjectPanel.h b/new_editor/app/Features/Project/ProjectPanel.h index add5d54a..114bc2e8 100644 --- a/new_editor/app/Features/Project/ProjectPanel.h +++ b/new_editor/app/Features/Project/ProjectPanel.h @@ -7,7 +7,6 @@ #include "Commands/EditorEditCommandRoute.h" #include #include -#include #include #include #include @@ -162,7 +161,6 @@ private: ::XCEngine::UI::UIRect browserHeaderRect = {}; ::XCEngine::UI::UIRect browserBodyRect = {}; ::XCEngine::UI::UIRect gridRect = {}; - Widgets::UIEditorScrollViewLayout gridScrollLayout = {}; std::vector breadcrumbItems = {}; std::vector assetTiles = {}; }; @@ -183,11 +181,6 @@ private: const UIEditorPanelContentHostPanelState* FindMountedProjectPanel( const UIEditorPanelContentHostFrame& contentHostFrame) const; Layout BuildLayout(const ::XCEngine::UI::UIRect& bounds) const; - void RebuildLayout(const ::XCEngine::UI::UIRect& bounds); - void ResetGridScrollPosition(); - void EnsureAssetVisible( - std::string_view itemId, - const ::XCEngine::UI::UIRect& bounds); std::size_t HitTestBreadcrumbItem(const ::XCEngine::UI::UIPoint& point) const; std::size_t HitTestAssetTile(const ::XCEngine::UI::UIPoint& point) const; std::string ResolveAssetDropTargetItemId( @@ -258,7 +251,6 @@ private: ::XCEngine::UI::Widgets::UISelectionModel m_assetSelection = {}; Collections::GridDragDrop::State m_assetDragState = {}; Collections::TreeDragDrop::State m_treeDragState = {}; - UIEditorScrollViewInteractionState m_gridScrollInteractionState = {}; UIEditorTreeViewInteractionState m_treeInteractionState = {}; UIEditorTreeViewInteractionFrame m_treeFrame = {}; UIEditorInlineRenameSessionState m_renameState = {}; @@ -273,7 +265,6 @@ private: std::string m_hoveredAssetItemId = {}; std::string m_lastPrimaryClickedAssetId = {}; float m_navigationWidth = 248.0f; - float m_gridVerticalOffset = 0.0f; std::chrono::steady_clock::time_point m_lastPrimaryClickTime = {}; std::size_t m_hoveredBreadcrumbIndex = static_cast(-1); std::size_t m_pressedBreadcrumbIndex = static_cast(-1); diff --git a/new_editor/app/Platform/Win32/EditorWindow.cpp b/new_editor/app/Platform/Win32/EditorWindow.cpp index b2b297dd..abc2cdcf 100644 --- a/new_editor/app/Platform/Win32/EditorWindow.cpp +++ b/new_editor/app/Platform/Win32/EditorWindow.cpp @@ -75,13 +75,6 @@ namespace XCEngine::UI::Editor::App { using namespace EditorWindowSupport; using ::XCEngine::UI::UIPoint; -namespace { - -constexpr UINT_PTR kFrameStatsRefreshTimerId = 0x58435549u; -constexpr UINT kFrameStatsRefreshIntervalMs = 250u; - -} - EditorWindow::EditorWindow( std::string windowId, std::wstring title, @@ -165,29 +158,20 @@ const UIEditorShellInteractionState& EditorWindow::GetShellInteractionState() co void EditorWindow::SetExternalDockHostDropPreview( const Widgets::UIEditorDockHostDropPreviewState& preview) { m_runtime->SetExternalDockHostDropPreview(preview); - RequestRender(); } void EditorWindow::ClearExternalDockHostDropPreview() { m_runtime->ClearExternalDockHostDropPreview(); - RequestRender(); } void EditorWindow::AttachHwnd(HWND hwnd) { m_state->window.hwnd = hwnd; m_state->window.closing = false; - m_state->window.renderRequested = true; } void EditorWindow::MarkDestroyed() { - if (m_state->window.hwnd != nullptr) { - KillTimer(m_state->window.hwnd, kFrameStatsRefreshTimerId); - } m_state->window.hwnd = nullptr; m_state->window.closing = false; - m_state->window.renderRequested = false; - m_state->window.renderingFrame = false; - m_state->window.frameStatsRefreshRequested = false; m_inputController->ResetWindowState(); } @@ -206,16 +190,16 @@ void EditorWindow::SetTrackingMouseLeave(bool trackingMouseLeave) { void EditorWindow::SetTitle(std::wstring title) { m_state->window.title = std::move(title); UpdateCachedTitleText(); - RequestRender(); } void EditorWindow::ReplaceWorkspaceController(UIEditorWorkspaceController workspaceController) { m_runtime->ReplaceWorkspaceController(std::move(workspaceController)); - RequestRender(); } -void EditorWindow::InvalidateHostWindow() { - RequestRender(); +void EditorWindow::InvalidateHostWindow() const { + if (m_state->window.hwnd != nullptr && IsWindow(m_state->window.hwnd)) { + InvalidateRect(m_state->window.hwnd, nullptr, FALSE); + } } bool EditorWindow::Initialize( @@ -238,28 +222,16 @@ bool EditorWindow::Initialize( << " scale=" << GetDpiScale(); LogRuntimeTrace("window", dpiTrace.str()); - const bool initialized = m_runtime->Initialize( + return m_runtime->Initialize( m_state->window.hwnd, repoRoot, editorContext, captureRoot, autoCaptureOnStartup); - if (initialized) { - SetTimer( - m_state->window.hwnd, - kFrameStatsRefreshTimerId, - kFrameStatsRefreshIntervalMs, - nullptr); - RequestRender(); - } - return initialized; } void EditorWindow::Shutdown() { ForceReleasePointerCapture(); - if (m_state->window.hwnd != nullptr && IsWindow(m_state->window.hwnd)) { - KillTimer(m_state->window.hwnd, kFrameStatsRefreshTimerId); - } m_runtime->Shutdown(); m_inputController->ClearPendingEvents(); @@ -278,7 +250,6 @@ void EditorWindow::ResetInteractionState() { m_chromeController->SetHoveredBorderlessResizeEdge( Host::BorderlessWindowResizeEdge::None); m_chromeController->ClearPredictedClientPixelSize(); - RequestRender(); } bool EditorWindow::ApplyWindowResize(UINT width, UINT height) { @@ -400,7 +371,6 @@ void EditorWindow::OnResize(UINT width, UINT height) { if (!matchesPredictedClientSize) { ApplyWindowResize(width, height); } - RequestRender(false); } void EditorWindow::OnEnterSizeMove() { @@ -415,7 +385,6 @@ void EditorWindow::OnExitSizeMove() { if (QueryCurrentClientPixelSize(width, height)) { ApplyWindowResize(width, height); } - RequestRender(false); } void EditorWindow::OnDpiChanged(UINT dpi, const RECT& suggestedRect) { @@ -444,7 +413,6 @@ void EditorWindow::OnDpiChanged(UINT dpi, const RECT& suggestedRect) { trace << "dpi changed to " << m_chromeController->GetWindowDpi() << " scale=" << GetDpiScale(); LogRuntimeTrace("window", trace.str()); - RequestRender(false); } bool EditorWindow::IsVerboseRuntimeTraceEnabled() { @@ -634,16 +602,11 @@ EditorWindowFrameTransferRequests EditorWindow::RenderFrame( return {}; } - const bool updateFrameTiming = !m_state->window.frameStatsRefreshRequested; - m_state->window.renderRequested = false; - m_state->window.frameStatsRefreshRequested = false; - UINT pixelWidth = 0u; UINT pixelHeight = 0u; if (!ResolveRenderClientPixelSize(pixelWidth, pixelHeight)) { return {}; } - m_state->window.renderingFrame = true; const float width = PixelsToDips(static_cast(pixelWidth)); const float height = PixelsToDips(static_cast(pixelHeight)); @@ -658,12 +621,7 @@ EditorWindowFrameTransferRequests EditorWindow::RenderFrame( EditorWindowFrameTransferRequests transferRequests = {}; if (editorContext.IsValid()) { transferRequests = - RenderRuntimeFrame( - editorContext, - globalTabDragActive, - updateFrameTiming, - workspaceBounds, - drawList); + RenderRuntimeFrame(editorContext, globalTabDragActive, workspaceBounds, drawList); } else { m_frameOrchestrator->AppendInvalidFrame(editorContext, drawList); } @@ -680,10 +638,6 @@ EditorWindowFrameTransferRequests EditorWindow::RenderFrame( pixelWidth, pixelHeight, presentResult.framePresented); - m_state->window.renderingFrame = false; - if (HasInteractiveCaptureState()) { - RequestRender(false); - } return transferRequests; } @@ -722,7 +676,6 @@ UIRect EditorWindow::ResolveWorkspaceBounds(float clientWidthDips, float clientH EditorWindowFrameTransferRequests EditorWindow::RenderRuntimeFrame( EditorContext& editorContext, bool globalTabDragActive, - bool updateFrameTiming, const UIRect& workspaceBounds, UIDrawList& drawList) { SyncShellCapturedPointerButtonsFromSystemState(); @@ -732,7 +685,6 @@ EditorWindowFrameTransferRequests EditorWindow::RenderRuntimeFrame( m_frameOrchestrator->UpdateAndAppend( editorContext, *m_runtime, - updateFrameTiming, workspaceBounds, frameEvents, m_runtime->BuildCaptureStatusText(), @@ -893,7 +845,6 @@ void EditorWindow::QueuePointerEvent( ConvertClientPixelsToDips(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)), wParam, doubleClick); - RequestRender(); } void EditorWindow::QueueSyntheticPointerStateSyncEvent( @@ -913,7 +864,6 @@ void EditorWindow::QueueSyntheticPointerStateSyncEvent( m_inputController->QueueSyntheticPointerStateSyncEvent( ConvertClientPixelsToDips(screenPoint.x, screenPoint.y), modifiers); - RequestRender(); } void EditorWindow::QueuePointerLeaveEvent() { @@ -925,7 +875,6 @@ void EditorWindow::QueuePointerLeaveEvent() { position = ConvertClientPixelsToDips(clientPoint.x, clientPoint.y); } m_inputController->QueuePointerLeaveEvent(position); - RequestRender(); } void EditorWindow::QueuePointerWheelEvent(short wheelDelta, WPARAM wParam, LPARAM lParam) { @@ -943,22 +892,18 @@ void EditorWindow::QueuePointerWheelEvent(short wheelDelta, WPARAM wParam, LPARA ConvertClientPixelsToDips(screenPoint.x, screenPoint.y), wheelDelta, wParam); - RequestRender(); } void EditorWindow::QueueKeyEvent(UIInputEventType type, WPARAM wParam, LPARAM lParam) { m_inputController->QueueKeyEvent(type, wParam, lParam); - RequestRender(); } void EditorWindow::QueueCharacterEvent(WPARAM wParam, LPARAM) { m_inputController->QueueCharacterEvent(wParam); - RequestRender(); } void EditorWindow::QueueWindowFocusEvent(UIInputEventType type) { m_inputController->QueueWindowFocusEvent(type); - RequestRender(); } void EditorWindow::SyncInputModifiersFromSystemState() { @@ -971,48 +916,6 @@ void EditorWindow::ResetInputModifiers() { void EditorWindow::RequestManualScreenshot() { m_runtime->RequestManualScreenshot("manual_f12"); - RequestRender(); -} - -bool EditorWindow::HandleFrameStatsTimer(UINT_PTR timerId) { - if (timerId != kFrameStatsRefreshTimerId) { - return false; - } - if (!m_runtime->IsReady() || - m_state->window.hwnd == nullptr || - !IsWindow(m_state->window.hwnd) || - IsIconic(m_state->window.hwnd)) { - return true; - } - if (HasInteractiveCaptureState() || HasPendingRenderRequest()) { - return true; - } - - RequestFrameStatsRefresh(); - return true; -} - -bool EditorWindow::HasPendingRenderRequest() const { - return m_state->window.renderRequested; -} - -void EditorWindow::RequestRender(bool invalidateHostWindow) { - m_state->window.renderRequested = true; - m_state->window.frameStatsRefreshRequested = false; - if (!invalidateHostWindow || - m_state->window.renderingFrame || - m_state->window.hwnd == nullptr || - !IsWindow(m_state->window.hwnd)) { - return; - } - - InvalidateRect(m_state->window.hwnd, nullptr, FALSE); -} - -void EditorWindow::RequestFrameStatsRefresh() { - m_state->window.frameStatsRefreshRequested = true; - RequestRender(false); - m_state->window.frameStatsRefreshRequested = true; } bool EditorWindow::IsPointerInsideClientArea() const { diff --git a/new_editor/app/Platform/Win32/EditorWindow.h b/new_editor/app/Platform/Win32/EditorWindow.h index 077e96d9..fd58012e 100644 --- a/new_editor/app/Platform/Win32/EditorWindow.h +++ b/new_editor/app/Platform/Win32/EditorWindow.h @@ -112,7 +112,7 @@ private: bool TryResolveDockTabDropTarget( const POINT& screenPoint, UIEditorDockHostTabDropTarget& outTarget) const; - void InvalidateHostWindow(); + void InvalidateHostWindow() const; void SetExternalDockHostDropPreview( const Widgets::UIEditorDockHostDropPreviewState& preview); void ClearExternalDockHostDropPreview(); @@ -204,10 +204,6 @@ private: void SyncShellCapturedPointerButtonsFromSystemState(); void ResetInputModifiers(); void RequestManualScreenshot(); - bool HandleFrameStatsTimer(UINT_PTR timerId); - bool HasPendingRenderRequest() const; - void RequestRender(bool invalidateHostWindow = true); - void RequestFrameStatsRefresh(); bool ApplyWindowResize(UINT width, UINT height); bool QueryCurrentClientPixelSize(UINT& outWidth, UINT& outHeight) const; @@ -218,7 +214,6 @@ private: EditorWindowFrameTransferRequests RenderRuntimeFrame( EditorContext& editorContext, bool globalTabDragActive, - bool updateFrameTiming, const ::XCEngine::UI::UIRect& workspaceBounds, ::XCEngine::UI::UIDrawList& drawList); bool IsPointerInsideClientArea() const; diff --git a/new_editor/app/Platform/Win32/EditorWindowFrameOrchestrator.cpp b/new_editor/app/Platform/Win32/EditorWindowFrameOrchestrator.cpp index 837341d0..a5e27421 100644 --- a/new_editor/app/Platform/Win32/EditorWindowFrameOrchestrator.cpp +++ b/new_editor/app/Platform/Win32/EditorWindowFrameOrchestrator.cpp @@ -63,7 +63,6 @@ std::string DescribeInputEventType(const UIInputEvent& event) { EditorWindowFrameTransferRequests EditorWindowFrameOrchestrator::UpdateAndAppend( EditorContext& editorContext, EditorWindowRuntimeController& runtimeController, - bool updateFrameTiming, const ::XCEngine::UI::UIRect& workspaceBounds, const std::vector& frameEvents, std::string_view captureStatusText, @@ -73,8 +72,7 @@ EditorWindowFrameTransferRequests EditorWindowFrameOrchestrator::UpdateAndAppend UIDrawList& drawList) const { LogInputTrace(editorContext, runtimeController, frameEvents); - const Host::D3D12WindowRenderLoopFrameContext frameContext = - runtimeController.BeginFrame(updateFrameTiming); + const Host::D3D12WindowRenderLoopFrameContext frameContext = runtimeController.BeginFrame(); if (!frameContext.warning.empty()) { LogRuntimeTrace("viewport", frameContext.warning); } diff --git a/new_editor/app/Platform/Win32/EditorWindowFrameOrchestrator.h b/new_editor/app/Platform/Win32/EditorWindowFrameOrchestrator.h index 06c16c16..74d559f4 100644 --- a/new_editor/app/Platform/Win32/EditorWindowFrameOrchestrator.h +++ b/new_editor/app/Platform/Win32/EditorWindowFrameOrchestrator.h @@ -44,7 +44,6 @@ public: EditorWindowFrameTransferRequests UpdateAndAppend( EditorContext& editorContext, EditorWindowRuntimeController& runtimeController, - bool updateFrameTiming, const ::XCEngine::UI::UIRect& workspaceBounds, const std::vector<::XCEngine::UI::UIInputEvent>& frameEvents, std::string_view captureStatusText, diff --git a/new_editor/app/Platform/Win32/EditorWindowManager.h b/new_editor/app/Platform/Win32/EditorWindowManager.h index 98ebfb1e..3e0d6b7e 100644 --- a/new_editor/app/Platform/Win32/EditorWindowManager.h +++ b/new_editor/app/Platform/Win32/EditorWindowManager.h @@ -85,7 +85,7 @@ public: bool HasWindows() const; void DestroyClosedWindows(); - bool RenderScheduledWindows(); + void RenderAllWindows(); private: std::unique_ptr m_hostRuntime = {}; diff --git a/new_editor/app/Platform/Win32/EditorWindowRuntimeController.cpp b/new_editor/app/Platform/Win32/EditorWindowRuntimeController.cpp index 482e9d30..36398302 100644 --- a/new_editor/app/Platform/Win32/EditorWindowRuntimeController.cpp +++ b/new_editor/app/Platform/Win32/EditorWindowRuntimeController.cpp @@ -18,8 +18,6 @@ using namespace EditorWindowSupport; namespace { constexpr float kFrameTimeSmoothingFactor = 0.12f; -constexpr float kFrameTimingResetThresholdSeconds = 0.5f; -constexpr float kFrameStatsIdleLabelThresholdSeconds = 0.35f; } @@ -75,7 +73,6 @@ void EditorWindowRuntimeController::ClearExternalDockHostDropPreview() { void EditorWindowRuntimeController::SetDpiScale(float dpiScale) { m_renderer.SetDpiScale(dpiScale); - m_windowRenderLoop.SetDpiScale(dpiScale); } Host::NativeRenderer& EditorWindowRuntimeController::GetRenderer() { @@ -195,16 +192,13 @@ bool EditorWindowRuntimeController::ApplyResize(UINT width, UINT height) { return resizeResult.hasViewportSurfacePresentation; } -Host::D3D12WindowRenderLoopFrameContext EditorWindowRuntimeController::BeginFrame( - bool updateFrameTiming) { - if (updateFrameTiming) { - UpdateFrameTiming(); - } +Host::D3D12WindowRenderLoopFrameContext EditorWindowRuntimeController::BeginFrame() { + UpdateFrameTiming(); return m_windowRenderLoop.BeginFrame(); } Host::D3D12WindowRenderLoopPresentResult EditorWindowRuntimeController::Present( - const ::XCEngine::UI::UIDrawData& drawData) { + const ::XCEngine::UI::UIDrawData& drawData) const { return m_windowRenderLoop.Present(drawData); } @@ -246,36 +240,19 @@ std::string EditorWindowRuntimeController::BuildFrameRateText() const { return {}; } - const auto now = std::chrono::steady_clock::now(); - const float idleSeconds = - m_hasLastMeasuredFrameTime - ? std::chrono::duration(now - m_lastMeasuredFrameTime).count() - : 0.0f; - char buffer[72] = {}; - if (idleSeconds >= kFrameStatsIdleLabelThresholdSeconds) { - std::snprintf( - buffer, - sizeof(buffer), - "FPS %.1f | %.2f ms | %.1fs ago", - m_displayFps, - m_displayFrameTimeMs, - idleSeconds); - } else { - std::snprintf( - buffer, - sizeof(buffer), - "FPS %.1f | %.2f ms", - m_displayFps, - m_displayFrameTimeMs); - } + char buffer[48] = {}; + std::snprintf( + buffer, + sizeof(buffer), + "FPS %.1f | %.2f ms", + m_displayFps, + m_displayFrameTimeMs); return buffer; } void EditorWindowRuntimeController::ResetFrameTiming() { m_lastFrameTime = {}; - m_lastMeasuredFrameTime = {}; m_hasLastFrameTime = false; - m_hasLastMeasuredFrameTime = false; m_smoothedDeltaTimeSeconds = 0.0f; m_displayFps = 0.0f; m_displayFrameTimeMs = 0.0f; @@ -294,9 +271,6 @@ void EditorWindowRuntimeController::UpdateFrameTiming() { if (deltaTime <= 0.0f) { return; } - if (deltaTime > kFrameTimingResetThresholdSeconds) { - return; - } if (m_smoothedDeltaTimeSeconds <= 0.0f) { m_smoothedDeltaTimeSeconds = deltaTime; @@ -309,8 +283,6 @@ void EditorWindowRuntimeController::UpdateFrameTiming() { m_displayFps = m_smoothedDeltaTimeSeconds > 0.0f ? 1.0f / m_smoothedDeltaTimeSeconds : 0.0f; - m_lastMeasuredFrameTime = now; - m_hasLastMeasuredFrameTime = true; } } // namespace XCEngine::UI::Editor::App diff --git a/new_editor/app/Platform/Win32/EditorWindowRuntimeController.h b/new_editor/app/Platform/Win32/EditorWindowRuntimeController.h index fe843da0..17cc11dc 100644 --- a/new_editor/app/Platform/Win32/EditorWindowRuntimeController.h +++ b/new_editor/app/Platform/Win32/EditorWindowRuntimeController.h @@ -77,9 +77,9 @@ public: void ResetInteractionState(); bool ApplyResize(UINT width, UINT height); - Host::D3D12WindowRenderLoopFrameContext BeginFrame(bool updateFrameTiming = true); + Host::D3D12WindowRenderLoopFrameContext BeginFrame(); Host::D3D12WindowRenderLoopPresentResult Present( - const ::XCEngine::UI::UIDrawData& drawData); + const ::XCEngine::UI::UIDrawData& drawData) const; void CaptureIfRequested( const ::XCEngine::UI::UIDrawData& drawData, UINT pixelWidth, @@ -102,9 +102,7 @@ private: UIEditorWorkspaceController m_workspaceController = {}; EditorShellRuntime m_shellRuntime = {}; std::chrono::steady_clock::time_point m_lastFrameTime = {}; - std::chrono::steady_clock::time_point m_lastMeasuredFrameTime = {}; bool m_hasLastFrameTime = false; - bool m_hasLastMeasuredFrameTime = false; float m_smoothedDeltaTimeSeconds = 0.0f; float m_displayFps = 0.0f; float m_displayFrameTimeMs = 0.0f; diff --git a/new_editor/app/Platform/Win32/EditorWindowState.h b/new_editor/app/Platform/Win32/EditorWindowState.h index 875b75f6..25260936 100644 --- a/new_editor/app/Platform/Win32/EditorWindowState.h +++ b/new_editor/app/Platform/Win32/EditorWindowState.h @@ -17,9 +17,6 @@ struct EditorWindowWindowState { std::string titleText = {}; bool primary = false; bool closing = false; - bool renderRequested = true; - bool renderingFrame = false; - bool frameStatsRefreshRequested = false; }; struct EditorWindowState { diff --git a/new_editor/app/Platform/Win32/WindowManager/EditorWindowHostRuntime.cpp b/new_editor/app/Platform/Win32/WindowManager/EditorWindowHostRuntime.cpp index 0048295f..764f70a6 100644 --- a/new_editor/app/Platform/Win32/WindowManager/EditorWindowHostRuntime.cpp +++ b/new_editor/app/Platform/Win32/WindowManager/EditorWindowHostRuntime.cpp @@ -159,7 +159,7 @@ void EditorWindowHostRuntime::DestroyClosedWindows() { } } -bool EditorWindowHostRuntime::RenderScheduledWindows( +void EditorWindowHostRuntime::RenderAllWindows( bool globalTabDragActive, EditorWindowWorkspaceCoordinator& workspaceCoordinator) { struct WindowFrameTransferBatch { @@ -169,22 +169,16 @@ bool EditorWindowHostRuntime::RenderScheduledWindows( std::vector transferBatches = {}; transferBatches.reserve(m_windows.size()); - bool renderedAnyWindow = false; for (const std::unique_ptr& window : m_windows) { if (window == nullptr || window->GetHwnd() == nullptr || - window->IsClosing() || - !window->HasPendingRenderRequest()) { + window->IsClosing()) { continue; } - renderedAnyWindow = true; EditorWindowFrameTransferRequests transferRequests = window->RenderFrame(m_editorContext, globalTabDragActive); - if (window->GetHwnd() != nullptr && IsWindow(window->GetHwnd())) { - ValidateRect(window->GetHwnd(), nullptr); - } workspaceCoordinator.CommitWindowProjection(*window); if (!transferRequests.HasPendingRequests()) { continue; @@ -207,8 +201,6 @@ bool EditorWindowHostRuntime::RenderScheduledWindows( *batch.sourceWindow, std::move(batch.requests)); } - - return renderedAnyWindow; } void EditorWindowHostRuntime::HandleDestroyedWindow(HWND hwnd) { diff --git a/new_editor/app/Platform/Win32/WindowManager/EditorWindowHostRuntime.h b/new_editor/app/Platform/Win32/WindowManager/EditorWindowHostRuntime.h index b90e2cc7..12e9ae77 100644 --- a/new_editor/app/Platform/Win32/WindowManager/EditorWindowHostRuntime.h +++ b/new_editor/app/Platform/Win32/WindowManager/EditorWindowHostRuntime.h @@ -38,7 +38,7 @@ public: bool HasWindows() const; void DestroyClosedWindows(); - bool RenderScheduledWindows( + void RenderAllWindows( bool globalTabDragActive, EditorWindowWorkspaceCoordinator& workspaceCoordinator); void HandleDestroyedWindow(HWND hwnd); diff --git a/new_editor/app/Platform/Win32/WindowManager/EditorWindowManager.cpp b/new_editor/app/Platform/Win32/WindowManager/EditorWindowManager.cpp index 8de26e91..950a6b7e 100644 --- a/new_editor/app/Platform/Win32/WindowManager/EditorWindowManager.cpp +++ b/new_editor/app/Platform/Win32/WindowManager/EditorWindowManager.cpp @@ -101,8 +101,8 @@ void EditorWindowManager::DestroyClosedWindows() { m_hostRuntime->DestroyClosedWindows(); } -bool EditorWindowManager::RenderScheduledWindows() { - return m_hostRuntime->RenderScheduledWindows( +void EditorWindowManager::RenderAllWindows() { + m_hostRuntime->RenderAllWindows( m_workspaceCoordinator->IsGlobalTabDragActive(), *m_workspaceCoordinator); } diff --git a/new_editor/app/Platform/Win32/WindowManager/EditorWindowMessageDispatcher.cpp b/new_editor/app/Platform/Win32/WindowManager/EditorWindowMessageDispatcher.cpp index 5f3f5cf8..21f7385e 100644 --- a/new_editor/app/Platform/Win32/WindowManager/EditorWindowMessageDispatcher.cpp +++ b/new_editor/app/Platform/Win32/WindowManager/EditorWindowMessageDispatcher.cpp @@ -397,12 +397,6 @@ bool EditorWindowMessageDispatcher::TryDispatchWindowLifecycleMessage( } outResult = 0; return true; - case WM_TIMER: - if (context.window.HandleFrameStatsTimer(static_cast(wParam))) { - outResult = 0; - return true; - } - return false; case WM_ERASEBKGND: outResult = 1; return true; diff --git a/new_editor/app/Ports/PortFwd.h b/new_editor/app/Ports/PortFwd.h index e51b6bf1..f659d2c4 100644 --- a/new_editor/app/Ports/PortFwd.h +++ b/new_editor/app/Ports/PortFwd.h @@ -5,7 +5,6 @@ namespace XCEngine::UI::Editor::Ports { class ShaderResourceDescriptorAllocatorPort; class SystemInteractionPort; class TexturePort; -class TextureDataPort; class ViewportRenderPort; } // namespace XCEngine::UI::Editor::Ports diff --git a/new_editor/app/Ports/TextureDataPort.h b/new_editor/app/Ports/TextureDataPort.h deleted file mode 100644 index cd26462c..00000000 --- a/new_editor/app/Ports/TextureDataPort.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#include - -#include - -namespace XCEngine::UI::Editor::Ports { - -struct TexturePixelDataView { - const std::uint8_t* pixels = nullptr; - std::uint32_t width = 0u; - std::uint32_t height = 0u; -}; - -class TextureDataPort { -public: - virtual ~TextureDataPort() = default; - - virtual bool ResolveTexturePixelData( - const ::XCEngine::UI::UITextureHandle& texture, - TexturePixelDataView& outView) const = 0; -}; - -} // namespace XCEngine::UI::Editor::Ports diff --git a/new_editor/app/Rendering/D3D12/D3D12HostDevice.cpp b/new_editor/app/Rendering/D3D12/D3D12HostDevice.cpp index 8673db5a..a82ff795 100644 --- a/new_editor/app/Rendering/D3D12/D3D12HostDevice.cpp +++ b/new_editor/app/Rendering/D3D12/D3D12HostDevice.cpp @@ -1,6 +1,4 @@ #include "D3D12HostDevice.h" -#include "Support/EnvironmentFlags.h" - #include namespace XCEngine::UI::Editor::Host { @@ -17,21 +15,6 @@ using ::XCEngine::RHI::RHIDeviceDesc; using ::XCEngine::RHI::RHIFactory; using ::XCEngine::RHI::RHIType; -#ifdef _DEBUG -namespace { - -bool ShouldEnableD3D12DebugLayer() { - return App::IsEnvironmentFlagEnabled("XCUIEDITOR_ENABLE_GPU_DEBUG") || - App::IsEnvironmentFlagEnabled("XCUIEDITOR_ENABLE_GPU_VALIDATION"); -} - -bool ShouldEnableD3D12GpuValidation() { - return App::IsEnvironmentFlagEnabled("XCUIEDITOR_ENABLE_GPU_VALIDATION"); -} - -} // namespace -#endif - bool D3D12HostDevice::Initialize() { Shutdown(); @@ -43,8 +26,8 @@ bool D3D12HostDevice::Initialize() { RHIDeviceDesc deviceDesc = {}; #ifdef _DEBUG - deviceDesc.enableDebugLayer = ShouldEnableD3D12DebugLayer(); - deviceDesc.enableGPUValidation = ShouldEnableD3D12GpuValidation(); + deviceDesc.enableDebugLayer = true; + deviceDesc.enableGPUValidation = true; #endif if (!m_device->Initialize(deviceDesc)) { m_lastError = "Failed to initialize the D3D12 RHI device."; diff --git a/new_editor/app/Rendering/D3D12/D3D12UIRenderer.cpp b/new_editor/app/Rendering/D3D12/D3D12UIRenderer.cpp deleted file mode 100644 index a2a2bb31..00000000 --- a/new_editor/app/Rendering/D3D12/D3D12UIRenderer.cpp +++ /dev/null @@ -1,1784 +0,0 @@ -#include "D3D12UIRenderer.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -namespace XCEngine::UI::Editor::Host { - -using ::XCEngine::UI::UIDrawCommand; -using ::XCEngine::UI::UIDrawCommandType; -using ::XCEngine::UI::UIDrawData; -using ::XCEngine::UI::UIDrawList; -using Microsoft::WRL::ComPtr; - -namespace { - -constexpr std::uint32_t kQuadPassConstantBinding = 0u; -constexpr float kAxisAlignedLineTolerance = 0.01f; -constexpr float kBaseDpi = 96.0f; -constexpr float kDefaultFontSize = 16.0f; - -struct QuadPassConstants { - float rectPx[4] = {}; - float color[4] = {}; - float viewportPx[2] = {}; - float uvMin[2] = {}; - float uvMax[2] = { 1.0f, 1.0f }; - float roundingPx = 0.0f; - float strokePx = 0.0f; -}; - -constexpr std::uint64_t AlignConstantBufferSize(std::uint64_t sizeInBytes) { - return (sizeInBytes + 255ull) & ~255ull; -} - -constexpr std::string_view kQuadVertexShaderSource = R"( -cbuffer QuadPassConstants : register(b0) { - float4 gRectPx; - float4 gColor; - float2 gViewportPx; - float2 gUvMin; - float2 gUvMax; - float gRoundingPx; - float gStrokePx; -}; - -struct VSOutput { - float4 position : SV_Position; - float4 color : COLOR0; - float2 localPx : TEXCOORD0; - float2 rectSizePx : TEXCOORD1; - float2 uv : TEXCOORD2; -}; - -VSOutput VSMain(uint vertexId : SV_VertexID) { - const float2 corners[6] = { - float2(0.0f, 0.0f), - float2(1.0f, 0.0f), - float2(0.0f, 1.0f), - float2(0.0f, 1.0f), - float2(1.0f, 0.0f), - float2(1.0f, 1.0f) - }; - - const float2 corner = corners[vertexId]; - const float2 pixelPosition = gRectPx.xy + corner * gRectPx.zw; - const float2 ndcPosition = float2( - (pixelPosition.x / gViewportPx.x) * 2.0f - 1.0f, - 1.0f - (pixelPosition.y / gViewportPx.y) * 2.0f); - - VSOutput output; - output.position = float4(ndcPosition, 0.0f, 1.0f); - output.color = gColor; - output.localPx = corner * gRectPx.zw; - output.rectSizePx = gRectPx.zw; - output.uv = lerp(gUvMin, gUvMax, corner); - return output; -} -)"; - -constexpr std::string_view kShapePixelShaderSource = R"( -cbuffer QuadPassConstants : register(b0) { - float4 gRectPx; - float4 gColor; - float2 gViewportPx; - float2 gUvMin; - float2 gUvMax; - float gRoundingPx; - float gStrokePx; -}; - -struct PSInput { - float4 position : SV_Position; - float4 color : COLOR0; - float2 localPx : TEXCOORD0; - float2 rectSizePx : TEXCOORD1; - float2 uv : TEXCOORD2; -}; - -float RoundedRectDistance(float2 localPx, float2 rectSizePx, float roundingPx) { - const float radius = min(gRoundingPx, min(rectSizePx.x, rectSizePx.y) * 0.5f); - const float2 halfSize = rectSizePx * 0.5f; - const float2 centerSpace = localPx - halfSize; - const float2 q = abs(centerSpace) - (halfSize - radius); - return length(max(q, 0.0f)) + min(max(q.x, q.y), 0.0f) - radius; -} - -float4 PSMain(PSInput input) : SV_Target0 { - const float distance = RoundedRectDistance(input.localPx, input.rectSizePx, gRoundingPx); - const float aa = max(fwidth(distance), 1.0f); - const float outerAlpha = 1.0f - smoothstep(0.0f, aa, distance); - float alpha = outerAlpha; - if (gStrokePx > 0.0f) { - const float innerAlpha = 1.0f - smoothstep(0.0f, aa, distance + gStrokePx); - alpha = saturate(outerAlpha - innerAlpha); - } - - return float4(input.color.rgb, input.color.a * alpha); -} -)"; - -constexpr std::string_view kImagePixelShaderSource = R"( -Texture2D gTexture : register(t0); -SamplerState gSampler : register(s0); - -cbuffer QuadPassConstants : register(b0) { - float4 gRectPx; - float4 gColor; - float2 gViewportPx; - float2 gUvMin; - float2 gUvMax; - float gRoundingPx; - float gStrokePx; -}; - -struct PSInput { - float4 position : SV_Position; - float4 color : COLOR0; - float2 localPx : TEXCOORD0; - float2 rectSizePx : TEXCOORD1; - float2 uv : TEXCOORD2; -}; - -float4 PSMain(PSInput input) : SV_Target0 { - const float4 sampleColor = gTexture.Sample(gSampler, input.uv); - const float4 premultipliedTint = float4(input.color.rgb * input.color.a, input.color.a); - return sampleColor * premultipliedTint; -} -)"; - -float ClampDpiScale(float dpiScale) { - return dpiScale > 0.0f ? dpiScale : 1.0f; -} - -float ResolveFontSize(float fontSize) { - return fontSize > 0.0f ? fontSize : kDefaultFontSize; -} - -std::vector ToShaderSource(std::string_view source) { - return std::vector(source.begin(), source.end()); -} - -std::wstring ToWideAscii(std::string_view value) { - return std::wstring(value.begin(), value.end()); -} - -std::wstring Utf8ToWide(std::string_view text) { - if (text.empty()) { - return {}; - } - - const int sizeNeeded = MultiByteToWideChar( - CP_UTF8, - 0, - text.data(), - static_cast(text.size()), - nullptr, - 0); - if (sizeNeeded <= 0) { - return {}; - } - - std::wstring wideText(static_cast(sizeNeeded), L'\0'); - MultiByteToWideChar( - CP_UTF8, - 0, - text.data(), - static_cast(text.size()), - wideText.data(), - sizeNeeded); - return wideText; -} - -::XCEngine::RHI::ShaderCompileDesc BuildShaderDesc( - std::string_view source, - std::string_view entryPoint, - std::string_view profile) { - ::XCEngine::RHI::ShaderCompileDesc desc = {}; - desc.source = ToShaderSource(source); - desc.entryPoint = ToWideAscii(entryPoint); - desc.profile = ToWideAscii(profile); - return desc; -} - -::XCEngine::RHI::Rect IntersectRects( - const ::XCEngine::RHI::Rect& left, - const ::XCEngine::RHI::Rect& right) { - ::XCEngine::RHI::Rect result = {}; - result.left = (std::max)(left.left, right.left); - result.top = (std::max)(left.top, right.top); - result.right = (std::min)(left.right, right.right); - result.bottom = (std::min)(left.bottom, right.bottom); - if (result.right < result.left) { - result.right = result.left; - } - if (result.bottom < result.top) { - result.bottom = result.top; - } - return result; -} - -bool IsEmptyRect(const ::XCEngine::RHI::Rect& rect) { - return rect.right <= rect.left || rect.bottom <= rect.top; -} - -::XCEngine::RHI::Rect BuildFullScissorRect( - std::uint32_t width, - std::uint32_t height) { - ::XCEngine::RHI::Rect rect = {}; - rect.left = 0; - rect.top = 0; - rect.right = static_cast(width); - rect.bottom = static_cast(height); - return rect; -} - -::XCEngine::RHI::Rect ToPixelRect( - const ::XCEngine::UI::UIRect& rect, - float dpiScale, - std::uint32_t viewportWidth, - std::uint32_t viewportHeight) { - const float scaledLeft = rect.x * dpiScale; - const float scaledTop = rect.y * dpiScale; - const float scaledRight = (rect.x + rect.width) * dpiScale; - const float scaledBottom = (rect.y + rect.height) * dpiScale; - - ::XCEngine::RHI::Rect result = {}; - result.left = static_cast(std::floor(scaledLeft)); - result.top = static_cast(std::floor(scaledTop)); - result.right = static_cast(std::ceil(scaledRight)); - result.bottom = static_cast(std::ceil(scaledBottom)); - - return IntersectRects(result, BuildFullScissorRect(viewportWidth, viewportHeight)); -} - -QuadPassConstants BuildQuadPassConstants( - const ::XCEngine::UI::UIRect& rect, - const ::XCEngine::UI::UIColor& color, - float dpiScale, - std::uint32_t viewportWidth, - std::uint32_t viewportHeight, - float rounding = 0.0f, - float strokeThickness = 0.0f, - const ::XCEngine::UI::UIPoint& uvMin = {}, - const ::XCEngine::UI::UIPoint& uvMax = ::XCEngine::UI::UIPoint(1.0f, 1.0f)) { - QuadPassConstants constants = {}; - constants.rectPx[0] = rect.x * dpiScale; - constants.rectPx[1] = rect.y * dpiScale; - constants.rectPx[2] = rect.width * dpiScale; - constants.rectPx[3] = rect.height * dpiScale; - constants.color[0] = color.r; - constants.color[1] = color.g; - constants.color[2] = color.b; - constants.color[3] = color.a; - constants.viewportPx[0] = static_cast(viewportWidth); - constants.viewportPx[1] = static_cast(viewportHeight); - constants.uvMin[0] = uvMin.x; - constants.uvMin[1] = uvMin.y; - constants.uvMax[0] = uvMax.x; - constants.uvMax[1] = uvMax.y; - constants.roundingPx = rounding * dpiScale; - constants.strokePx = strokeThickness * dpiScale; - return constants; -} - -bool TryBuildAxisAlignedLineRect( - const ::XCEngine::UI::UIDrawCommand& command, - ::XCEngine::UI::UIRect& outRect) { - outRect = {}; - const float thickness = command.thickness > 0.0f ? command.thickness : 1.0f; - const float dx = command.uvMin.x - command.position.x; - const float dy = command.uvMin.y - command.position.y; - if (std::fabs(dx) <= kAxisAlignedLineTolerance) { - const float x = command.position.x - thickness * 0.5f; - const float top = (std::min)(command.position.y, command.uvMin.y); - const float height = std::fabs(dy); - outRect = ::XCEngine::UI::UIRect(x, top, thickness, height); - return true; - } - - if (std::fabs(dy) <= kAxisAlignedLineTolerance) { - const float y = command.position.y - thickness * 0.5f; - const float left = (std::min)(command.position.x, command.uvMin.x); - const float width = std::fabs(dx); - outRect = ::XCEngine::UI::UIRect(left, y, width, thickness); - return true; - } - - return false; -} - -std::vector ConvertPremultipliedBgraToPremultipliedRgba( - const std::uint8_t* pixels, - std::uint32_t width, - std::uint32_t height) { - const std::uint64_t pixelCount = - static_cast(width) * static_cast(height); - std::vector rgbaPixels(static_cast(pixelCount) * 4u); - for (std::uint64_t pixelIndex = 0u; pixelIndex < pixelCount; ++pixelIndex) { - const std::size_t offset = static_cast(pixelIndex) * 4u; - rgbaPixels[offset + 0u] = pixels[offset + 2u]; - rgbaPixels[offset + 1u] = pixels[offset + 1u]; - rgbaPixels[offset + 2u] = pixels[offset + 0u]; - rgbaPixels[offset + 3u] = pixels[offset + 3u]; - } - return rgbaPixels; -} - -std::string DescribeUnsupportedCommand( - const ::XCEngine::UI::UIDrawCommand& command, - std::size_t drawListIndex, - std::size_t commandIndex) { - const std::string location = - "drawList=" + std::to_string(drawListIndex) + - " command=" + std::to_string(commandIndex) + ": "; - switch (command.type) { - case ::XCEngine::UI::UIDrawCommandType::FilledRectLinearGradient: - return location + "linear gradient fill is not implemented in the native D3D12 UI pass yet."; - case ::XCEngine::UI::UIDrawCommandType::Line: - return location + "non-axis-aligned line is not implemented in the native D3D12 UI pass yet."; - case ::XCEngine::UI::UIDrawCommandType::FilledTriangle: - return location + "filled triangle is not implemented in the native D3D12 UI pass yet."; - case ::XCEngine::UI::UIDrawCommandType::FilledCircle: - return location + "filled circle is not implemented in the native D3D12 UI pass yet."; - case ::XCEngine::UI::UIDrawCommandType::CircleOutline: - return location + "circle outline is not implemented in the native D3D12 UI pass yet."; - default: - return location + "unsupported UI command."; - } -} - -} // namespace - -bool D3D12UIRenderer::AttachWindowRenderer(D3D12WindowRenderer& windowRenderer) { - m_windowRenderer = &windowRenderer; - m_lastError.clear(); - return true; -} - -void D3D12UIRenderer::DetachWindowRenderer() { - ReleaseResources(); - m_windowRenderer = nullptr; - m_textureDataSource = nullptr; - m_lastFrameAnalysis = {}; - m_lastError.clear(); -} - -void D3D12UIRenderer::SetTextureDataSource(const Ports::TextureDataPort* textureDataSource) { - m_textureDataSource = textureDataSource; -} - -bool D3D12UIRenderer::HasAttachedWindowRenderer() const { - return m_windowRenderer != nullptr; -} - -void D3D12UIRenderer::SetDpiScale(float dpiScale) { - m_dpiScale = ClampDpiScale(dpiScale); -} - -float D3D12UIRenderer::GetDpiScale() const { - return m_dpiScale; -} - -void D3D12UIRenderer::AnalyzeFrame(const UIDrawData& drawData) { - m_lastFrameAnalysis = BuildFrameAnalysis(drawData); -} - -bool D3D12UIRenderer::CanRender(const UIDrawData& drawData, std::string* outReason) { - AnalyzeFrame(drawData); - - if (m_windowRenderer == nullptr) { - m_lastError = "Native D3D12 UI renderer requires an attached window renderer."; - if (outReason != nullptr) { - *outReason = m_lastError; - } - return false; - } - - if (m_lastFrameAnalysis.empty) { - m_lastError.clear(); - if (outReason != nullptr) { - outReason->clear(); - } - return true; - } - - const ::XCEngine::Rendering::RenderSurface* targetSurface = - m_windowRenderer->GetCurrentRenderSurface(); - if (targetSurface == nullptr) { - m_lastError = "Native D3D12 UI renderer could not resolve the current window render surface."; - if (outReason != nullptr) { - *outReason = m_lastError; - } - return false; - } - - if (!EnsureShapePassResources(*targetSurface)) { - if (outReason != nullptr) { - *outReason = m_lastError; - } - return false; - } - if ((m_lastFrameAnalysis.requiresImages || m_lastFrameAnalysis.requiresText) && - !EnsureImagePassResources(*targetSurface)) { - if (outReason != nullptr) { - *outReason = m_lastError; - } - return false; - } - if (m_lastFrameAnalysis.requiresText && !EnsureTextRasterizer()) { - if (outReason != nullptr) { - *outReason = m_lastError; - } - return false; - } - - std::string reason = {}; - if (!ValidateSupportedFrame(drawData, &reason)) { - m_lastError = std::move(reason); - if (outReason != nullptr) { - *outReason = m_lastError; - } - return false; - } - - m_lastError.clear(); - if (outReason != nullptr) { - outReason->clear(); - } - return true; -} - -bool D3D12UIRenderer::Render(const UIDrawData& drawData) { - std::string reason = {}; - if (!CanRender(drawData, &reason)) { - m_lastError = std::move(reason); - return false; - } - - return RenderSupportedFrame(drawData); -} - -const D3D12UIRendererFrameAnalysis& D3D12UIRenderer::GetLastFrameAnalysis() const { - return m_lastFrameAnalysis; -} - -const std::string& D3D12UIRenderer::GetLastError() const { - return m_lastError; -} - -void D3D12UIRenderer::ReleaseResources() { - ReleasePipelineResources(); - ReleaseUploadedTextureCaches(); - ReleaseTextRasterizer(); -} - -void D3D12UIRenderer::ReleasePipelineResources() { - if (m_shapePipelineState != nullptr) { - m_shapePipelineState->Shutdown(); - delete m_shapePipelineState; - m_shapePipelineState = nullptr; - } - - if (m_shapePipelineLayout != nullptr) { - m_shapePipelineLayout->Shutdown(); - delete m_shapePipelineLayout; - m_shapePipelineLayout = nullptr; - } - - if (m_imagePipelineState != nullptr) { - m_imagePipelineState->Shutdown(); - delete m_imagePipelineState; - m_imagePipelineState = nullptr; - } - - if (m_imagePipelineLayout != nullptr) { - m_imagePipelineLayout->Shutdown(); - delete m_imagePipelineLayout; - m_imagePipelineLayout = nullptr; - } - - if (m_imageSamplerHeap != nullptr) { - m_imageSamplerHeap->Shutdown(); - delete m_imageSamplerHeap; - m_imageSamplerHeap = nullptr; - } - m_imageSamplerCpuHandle = {}; - m_imageSamplerGpuHandle = {}; - - ReleaseFrameConstantBuffers(); - m_cachedRenderTargetFormat = 0u; - m_cachedSampleCount = 0u; - m_cachedSampleQuality = 0u; -} - -void D3D12UIRenderer::ReleaseFrameConstantBuffers() { - for (auto*& buffer : m_frameConstantBuffers) { - if (buffer != nullptr) { - buffer->Shutdown(); - delete buffer; - buffer = nullptr; - } - } - - m_frameConstantBuffers.clear(); - m_frameConstantBufferCapacities.clear(); -} - -void D3D12UIRenderer::ReleaseUploadedTexture(UploadedTextureCacheEntry& texture) { - if (texture.texture != nullptr) { - texture.texture->Shutdown(); - delete texture.texture; - texture.texture = nullptr; - } - if (texture.cpuHandle.ptr != 0u || texture.gpuHandle.ptr != 0u) { - m_uploadedTextureAllocator.Free(texture.cpuHandle, texture.gpuHandle); - } - - texture = {}; -} - -void D3D12UIRenderer::ReleaseUploadedTextureCaches() { - for (auto& entry : m_uploadedTextureCache) { - ReleaseUploadedTexture(entry.second); - } - m_uploadedTextureCache.clear(); - - for (auto& entry : m_cachedTextTextures) { - ReleaseUploadedTexture(entry.second.uploaded); - } - m_cachedTextTextures.clear(); - - m_uploadedTextureAllocator.Shutdown(); -} - -void D3D12UIRenderer::ReleaseTextRasterizer() { - m_textFormats.clear(); - m_textWicFactory.Reset(); - m_textDWriteFactory.Reset(); - m_textD2DFactory.Reset(); - if (m_textComInitialized) { - CoUninitialize(); - m_textComInitialized = false; - } -} - -bool D3D12UIRenderer::EnsureShapePassResources( - const ::XCEngine::Rendering::RenderSurface& targetSurface) { - if (m_windowRenderer == nullptr) { - m_lastError = "Native D3D12 UI renderer requires an attached window renderer."; - return false; - } - - if (!::XCEngine::Rendering::HasSingleColorAttachment(targetSurface)) { - m_lastError = "Native D3D12 UI renderer requires a single-color render target."; - return false; - } - - const std::uint32_t renderTargetFormat = static_cast( - ::XCEngine::Rendering::ResolveSurfaceColorFormat(targetSurface, 0u)); - const std::uint32_t sampleCount = - ::XCEngine::Rendering::ResolveSurfaceSampleCount(targetSurface); - const std::uint32_t sampleQuality = - ::XCEngine::Rendering::ResolveSurfaceSampleQuality(targetSurface); - if (renderTargetFormat == - static_cast(::XCEngine::RHI::Format::Unknown)) { - m_lastError = "Native D3D12 UI renderer could not resolve the swap chain color format."; - return false; - } - - if (m_shapePipelineLayout != nullptr && - m_shapePipelineState != nullptr && - m_cachedRenderTargetFormat == renderTargetFormat && - m_cachedSampleCount == sampleCount && - m_cachedSampleQuality == sampleQuality) { - return true; - } - - ReleasePipelineResources(); - - ::XCEngine::RHI::RHIDevice* device = m_windowRenderer->GetRHIDevice(); - if (device == nullptr) { - m_lastError = "Native D3D12 UI renderer could not resolve the host RHI device."; - return false; - } - - ::XCEngine::RHI::RHIPipelineLayoutDesc pipelineLayoutDesc = {}; - pipelineLayoutDesc.constantBufferCount = 1u; - m_shapePipelineLayout = device->CreatePipelineLayout(pipelineLayoutDesc); - if (m_shapePipelineLayout == nullptr) { - m_lastError = "Native D3D12 UI renderer failed to create the shape-pass pipeline layout."; - ReleasePipelineResources(); - return false; - } - - ::XCEngine::RHI::GraphicsPipelineDesc pipelineDesc = {}; - pipelineDesc.pipelineLayout = m_shapePipelineLayout; - pipelineDesc.topologyType = - static_cast(::XCEngine::RHI::PrimitiveTopologyType::Triangle); - pipelineDesc.vertexShader = BuildShaderDesc(kQuadVertexShaderSource, "VSMain", "vs_5_0"); - pipelineDesc.fragmentShader = BuildShaderDesc(kShapePixelShaderSource, "PSMain", "ps_5_0"); - pipelineDesc.rasterizerState.cullMode = - static_cast(::XCEngine::RHI::CullMode::None); - pipelineDesc.rasterizerState.scissorTestEnable = true; - pipelineDesc.blendState.blendEnable = true; - pipelineDesc.blendState.srcBlend = - static_cast(::XCEngine::RHI::BlendFactor::SrcAlpha); - pipelineDesc.blendState.dstBlend = - static_cast(::XCEngine::RHI::BlendFactor::InvSrcAlpha); - pipelineDesc.blendState.srcBlendAlpha = - static_cast(::XCEngine::RHI::BlendFactor::One); - pipelineDesc.blendState.dstBlendAlpha = - static_cast(::XCEngine::RHI::BlendFactor::InvSrcAlpha); - pipelineDesc.depthStencilState.depthTestEnable = false; - pipelineDesc.depthStencilState.depthWriteEnable = false; - ::XCEngine::Rendering::ApplySingleColorAttachmentPropertiesToGraphicsPipelineDesc( - targetSurface, - pipelineDesc); - - m_shapePipelineState = device->CreatePipelineState(pipelineDesc); - if (m_shapePipelineState == nullptr || !m_shapePipelineState->IsValid()) { - m_lastError = "Native D3D12 UI renderer failed to create the shape-pass pipeline state."; - ReleasePipelineResources(); - return false; - } - - m_cachedRenderTargetFormat = renderTargetFormat; - m_cachedSampleCount = sampleCount; - m_cachedSampleQuality = sampleQuality; - m_lastError.clear(); - return true; -} - -bool D3D12UIRenderer::EnsureImagePassResources( - const ::XCEngine::Rendering::RenderSurface& targetSurface) { - if (!EnsureShapePassResources(targetSurface)) { - return false; - } - - if (m_imagePipelineLayout != nullptr && - m_imagePipelineState != nullptr && - m_imageSamplerHeap != nullptr && - m_uploadedTextureAllocator.IsInitialized()) { - return true; - } - - ::XCEngine::RHI::RHIDevice* device = m_windowRenderer->GetRHIDevice(); - ID3D12Device* nativeDevice = m_windowRenderer->GetDevice(); - if (device == nullptr || nativeDevice == nullptr) { - m_lastError = "Native D3D12 UI renderer could not resolve the host D3D12 device."; - return false; - } - - if (!m_uploadedTextureAllocator.IsInitialized() && - !m_uploadedTextureAllocator.Initialize(*device, 256u)) { - m_lastError = "Native D3D12 UI renderer failed to initialize the uploaded-texture descriptor allocator."; - return false; - } - - auto* samplerHeap = new ::XCEngine::RHI::D3D12DescriptorHeap(); - if (!samplerHeap->Initialize( - nativeDevice, - ::XCEngine::RHI::DescriptorHeapType::Sampler, - 1u, - true)) { - delete samplerHeap; - m_lastError = "Native D3D12 UI renderer failed to create the image sampler heap."; - return false; - } - - D3D12_SAMPLER_DESC samplerDesc = {}; - samplerDesc.Filter = D3D12_FILTER_MIN_MAG_MIP_LINEAR; - samplerDesc.AddressU = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; - samplerDesc.AddressV = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; - samplerDesc.AddressW = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; - samplerDesc.MinLOD = 0.0f; - samplerDesc.MaxLOD = D3D12_FLOAT32_MAX; - nativeDevice->CreateSampler( - &samplerDesc, - samplerHeap->GetCPUDescriptorHandleForHeapStart()); - m_imageSamplerHeap = samplerHeap; - m_imageSamplerCpuHandle = samplerHeap->GetCPUDescriptorHandleForHeapStart(); - m_imageSamplerGpuHandle = samplerHeap->GetGPUDescriptorHandleForHeapStart(); - - ::XCEngine::RHI::RHIPipelineLayoutDesc pipelineLayoutDesc = {}; - pipelineLayoutDesc.constantBufferCount = 1u; - pipelineLayoutDesc.textureCount = 1u; - pipelineLayoutDesc.samplerCount = 1u; - m_imagePipelineLayout = device->CreatePipelineLayout(pipelineLayoutDesc); - if (m_imagePipelineLayout == nullptr) { - m_lastError = "Native D3D12 UI renderer failed to create the image-pass pipeline layout."; - ReleasePipelineResources(); - return false; - } - - ::XCEngine::RHI::GraphicsPipelineDesc pipelineDesc = {}; - pipelineDesc.pipelineLayout = m_imagePipelineLayout; - pipelineDesc.topologyType = - static_cast(::XCEngine::RHI::PrimitiveTopologyType::Triangle); - pipelineDesc.vertexShader = BuildShaderDesc(kQuadVertexShaderSource, "VSMain", "vs_5_0"); - pipelineDesc.fragmentShader = BuildShaderDesc(kImagePixelShaderSource, "PSMain", "ps_5_0"); - pipelineDesc.rasterizerState.cullMode = - static_cast(::XCEngine::RHI::CullMode::None); - pipelineDesc.rasterizerState.scissorTestEnable = true; - pipelineDesc.blendState.blendEnable = true; - pipelineDesc.blendState.srcBlend = - static_cast(::XCEngine::RHI::BlendFactor::One); - pipelineDesc.blendState.dstBlend = - static_cast(::XCEngine::RHI::BlendFactor::InvSrcAlpha); - pipelineDesc.blendState.srcBlendAlpha = - static_cast(::XCEngine::RHI::BlendFactor::One); - pipelineDesc.blendState.dstBlendAlpha = - static_cast(::XCEngine::RHI::BlendFactor::InvSrcAlpha); - pipelineDesc.depthStencilState.depthTestEnable = false; - pipelineDesc.depthStencilState.depthWriteEnable = false; - ::XCEngine::Rendering::ApplySingleColorAttachmentPropertiesToGraphicsPipelineDesc( - targetSurface, - pipelineDesc); - - m_imagePipelineState = device->CreatePipelineState(pipelineDesc); - if (m_imagePipelineState == nullptr || !m_imagePipelineState->IsValid()) { - m_lastError = "Native D3D12 UI renderer failed to create the image-pass pipeline state."; - ReleasePipelineResources(); - return false; - } - - m_lastError.clear(); - return true; -} - -bool D3D12UIRenderer::EnsureFrameConstantBufferCapacity( - std::uint32_t frameSlot, - std::size_t requiredDrawCount) { - if (m_windowRenderer == nullptr) { - m_lastError = "Native D3D12 UI renderer requires an attached window renderer."; - return false; - } - - if (frameSlot >= D3D12WindowRenderer::kFrameContextCount) { - m_lastError = "Native D3D12 UI renderer received an invalid frame slot."; - return false; - } - - if (m_frameConstantBuffers.size() < D3D12WindowRenderer::kFrameContextCount) { - m_frameConstantBuffers.resize(D3D12WindowRenderer::kFrameContextCount, nullptr); - m_frameConstantBufferCapacities.resize(D3D12WindowRenderer::kFrameContextCount, 0u); - } - - const std::uint64_t alignedConstantSize = - AlignConstantBufferSize(sizeof(QuadPassConstants)); - const std::uint64_t requiredCapacity = - alignedConstantSize * (std::max)(requiredDrawCount, 1u); - if (m_frameConstantBuffers[frameSlot] != nullptr && - m_frameConstantBufferCapacities[frameSlot] >= requiredCapacity) { - return true; - } - - ID3D12Device* nativeDevice = m_windowRenderer->GetDevice(); - if (nativeDevice == nullptr) { - m_lastError = "Native D3D12 UI renderer could not resolve the host D3D12 device."; - return false; - } - - auto* newBuffer = new ::XCEngine::RHI::D3D12Buffer(); - if (!newBuffer->Initialize( - nativeDevice, - requiredCapacity, - D3D12_RESOURCE_STATE_GENERIC_READ, - D3D12_HEAP_TYPE_UPLOAD)) { - delete newBuffer; - m_lastError = "Native D3D12 UI renderer failed to create the per-frame constant buffer."; - return false; - } - - newBuffer->SetBufferType(::XCEngine::RHI::BufferType::Constant); - newBuffer->SetStride(static_cast(alignedConstantSize)); - if (m_frameConstantBuffers[frameSlot] != nullptr) { - m_frameConstantBuffers[frameSlot]->Shutdown(); - delete m_frameConstantBuffers[frameSlot]; - } - - m_frameConstantBuffers[frameSlot] = newBuffer; - m_frameConstantBufferCapacities[frameSlot] = requiredCapacity; - return true; -} - -bool D3D12UIRenderer::EnsureTextRasterizer() { - if (m_textD2DFactory != nullptr && - m_textDWriteFactory != nullptr && - m_textWicFactory != nullptr) { - return true; - } - - const HRESULT initHr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); - if (FAILED(initHr) && initHr != RPC_E_CHANGED_MODE) { - m_lastError = "Native D3D12 UI renderer failed to initialize COM for text rasterization."; - return false; - } - if (SUCCEEDED(initHr)) { - m_textComInitialized = true; - } - - D2D1_FACTORY_OPTIONS factoryOptions = {}; - HRESULT hr = D2D1CreateFactory( - D2D1_FACTORY_TYPE_SINGLE_THREADED, - __uuidof(ID2D1Factory1), - &factoryOptions, - reinterpret_cast(m_textD2DFactory.ReleaseAndGetAddressOf())); - if (FAILED(hr) || m_textD2DFactory == nullptr) { - m_lastError = "Native D3D12 UI renderer failed to create the offline D2D factory."; - return false; - } - - hr = DWriteCreateFactory( - DWRITE_FACTORY_TYPE_SHARED, - __uuidof(IDWriteFactory), - reinterpret_cast(m_textDWriteFactory.ReleaseAndGetAddressOf())); - if (FAILED(hr) || m_textDWriteFactory == nullptr) { - m_lastError = "Native D3D12 UI renderer failed to create the offline DWrite factory."; - return false; - } - - hr = CoCreateInstance( - CLSID_WICImagingFactory, - nullptr, - CLSCTX_INPROC_SERVER, - IID_PPV_ARGS(m_textWicFactory.ReleaseAndGetAddressOf())); - if (FAILED(hr) || m_textWicFactory == nullptr) { - m_lastError = "Native D3D12 UI renderer failed to create the offline WIC factory."; - return false; - } - - m_lastError.clear(); - return true; -} - -IDWriteTextFormat* D3D12UIRenderer::GetTextFormat(float scaledFontSize) { - if (m_textDWriteFactory == nullptr) { - return nullptr; - } - - const int key = static_cast(std::lround(scaledFontSize * 10.0f)); - const auto found = m_textFormats.find(key); - if (found != m_textFormats.end()) { - return found->second.Get(); - } - - ComPtr textFormat = {}; - const HRESULT hr = m_textDWriteFactory->CreateTextFormat( - L"Segoe UI", - nullptr, - DWRITE_FONT_WEIGHT_REGULAR, - DWRITE_FONT_STYLE_NORMAL, - DWRITE_FONT_STRETCH_NORMAL, - scaledFontSize, - L"", - textFormat.ReleaseAndGetAddressOf()); - if (FAILED(hr) || textFormat == nullptr) { - return nullptr; - } - - textFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_LEADING); - textFormat->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_NEAR); - textFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP); - - IDWriteTextFormat* result = textFormat.Get(); - m_textFormats.emplace(key, std::move(textFormat)); - return result; -} - -bool D3D12UIRenderer::ValidateSupportedFrame( - const UIDrawData& drawData, - std::string* outReason) { - if (outReason != nullptr) { - outReason->clear(); - } - - for (std::size_t drawListIndex = 0u; - drawListIndex < drawData.GetDrawLists().size(); - ++drawListIndex) { - const UIDrawList& drawList = drawData.GetDrawLists()[drawListIndex]; - for (std::size_t commandIndex = 0u; - commandIndex < drawList.GetCommands().size(); - ++commandIndex) { - const UIDrawCommand& command = drawList.GetCommands()[commandIndex]; - switch (command.type) { - case UIDrawCommandType::FilledRect: - case UIDrawCommandType::RectOutline: - continue; - case UIDrawCommandType::Line: { - ::XCEngine::UI::UIRect lineRect = {}; - if (TryBuildAxisAlignedLineRect(command, lineRect)) { - continue; - } - break; - } - case UIDrawCommandType::Image: { - ID3D12DescriptorHeap* descriptorHeap = nullptr; - D3D12_GPU_DESCRIPTOR_HANDLE descriptor = {}; - std::string imageReason = {}; - if (ResolveUploadedTextureBinding( - command.texture, - &descriptorHeap, - &descriptor, - &imageReason)) { - continue; - } - if (outReason != nullptr) { - *outReason = imageReason; - } - return false; - } - case UIDrawCommandType::Text: { - ID3D12DescriptorHeap* descriptorHeap = nullptr; - D3D12_GPU_DESCRIPTOR_HANDLE descriptor = {}; - ::XCEngine::UI::UIRect textRect = {}; - std::string textReason = {}; - if (ResolveTextTextureBinding( - command, - &descriptorHeap, - &descriptor, - textRect, - &textReason)) { - continue; - } - if (outReason != nullptr) { - *outReason = textReason; - } - return false; - } - case UIDrawCommandType::PushClipRect: - case UIDrawCommandType::PopClipRect: - continue; - default: - break; - } - - if (outReason != nullptr) { - *outReason = DescribeUnsupportedCommand(command, drawListIndex, commandIndex); - } - return false; - } - } - - return true; -} - -bool D3D12UIRenderer::ResolveUploadedTextureBinding( - const ::XCEngine::UI::UITextureHandle& texture, - ID3D12DescriptorHeap** outDescriptorHeap, - D3D12_GPU_DESCRIPTOR_HANDLE* outGpuHandle, - std::string* outReason) { - if (outDescriptorHeap != nullptr) { - *outDescriptorHeap = nullptr; - } - if (outGpuHandle != nullptr) { - *outGpuHandle = {}; - } - if (outReason != nullptr) { - outReason->clear(); - } - - if (!texture.IsValid()) { - if (outReason != nullptr) { - *outReason = "image command references an invalid texture handle."; - } - return false; - } - - if (texture.kind == ::XCEngine::UI::UITextureHandleKind::ShaderResourceView) { - if (m_windowRenderer == nullptr || - m_windowRenderer->GetViewportTextureDescriptorHeap() == nullptr || - texture.nativeHandle == 0u) { - if (outReason != nullptr) { - *outReason = "image command references an unresolved shader-resource texture handle."; - } - return false; - } - - if (outDescriptorHeap != nullptr) { - *outDescriptorHeap = m_windowRenderer->GetViewportTextureDescriptorHeap(); - } - if (outGpuHandle != nullptr) { - outGpuHandle->ptr = static_cast(texture.nativeHandle); - } - return true; - } - - if (texture.kind != ::XCEngine::UI::UITextureHandleKind::DescriptorHandle || - m_textureDataSource == nullptr) { - if (outReason != nullptr) { - *outReason = "image command requires a CPU texture data source that is unavailable."; - } - return false; - } - - Ports::TexturePixelDataView pixelData = {}; - if (!m_textureDataSource->ResolveTexturePixelData(texture, pixelData) || - pixelData.pixels == nullptr || - pixelData.width == 0u || - pixelData.height == 0u) { - if (outReason != nullptr) { - *outReason = "image command references a released or unresolved CPU texture."; - } - return false; - } - - auto& cachedTexture = m_uploadedTextureCache[texture.nativeHandle]; - if (cachedTexture.texture == nullptr || - cachedTexture.sourcePixels != pixelData.pixels || - cachedTexture.width != pixelData.width || - cachedTexture.height != pixelData.height) { - ReleaseUploadedTexture(cachedTexture); - - if (!m_uploadedTextureAllocator.IsInitialized()) { - if (outReason != nullptr) { - *outReason = "native uploaded-texture descriptor allocator is not initialized."; - } - return false; - } - - ::XCEngine::RHI::RHIDevice* device = m_windowRenderer->GetRHIDevice(); - if (device == nullptr) { - if (outReason != nullptr) { - *outReason = "native D3D12 UI renderer could not resolve the host RHI device."; - } - return false; - } - - const std::vector rgbaPixels = - ConvertPremultipliedBgraToPremultipliedRgba( - pixelData.pixels, - pixelData.width, - pixelData.height); - ::XCEngine::RHI::TextureDesc textureDesc = {}; - textureDesc.width = pixelData.width; - textureDesc.height = pixelData.height; - textureDesc.depth = 1u; - textureDesc.mipLevels = 1u; - textureDesc.arraySize = 1u; - textureDesc.format = - static_cast(::XCEngine::RHI::Format::R8G8B8A8_UNorm); - textureDesc.textureType = - static_cast(::XCEngine::RHI::TextureType::Texture2D); - textureDesc.sampleCount = 1u; - textureDesc.sampleQuality = 0u; - textureDesc.flags = 0u; - cachedTexture.texture = device->CreateTexture( - textureDesc, - rgbaPixels.data(), - rgbaPixels.size(), - pixelData.width * 4u); - if (cachedTexture.texture == nullptr) { - if (outReason != nullptr) { - *outReason = "native D3D12 UI renderer failed to upload a CPU texture to the GPU."; - } - ReleaseUploadedTexture(cachedTexture); - return false; - } - - if (!m_uploadedTextureAllocator.CreateTextureDescriptor( - cachedTexture.texture, - &cachedTexture.cpuHandle, - &cachedTexture.gpuHandle)) { - if (outReason != nullptr) { - *outReason = "native D3D12 UI renderer failed to allocate a GPU descriptor for a CPU texture."; - } - ReleaseUploadedTexture(cachedTexture); - return false; - } - - cachedTexture.sourcePixels = pixelData.pixels; - cachedTexture.width = pixelData.width; - cachedTexture.height = pixelData.height; - } - - if (outDescriptorHeap != nullptr) { - *outDescriptorHeap = m_uploadedTextureAllocator.GetDescriptorHeap(); - } - if (outGpuHandle != nullptr) { - *outGpuHandle = cachedTexture.gpuHandle; - } - return true; -} - -bool D3D12UIRenderer::ResolveTextTextureBinding( - const ::XCEngine::UI::UIDrawCommand& command, - ID3D12DescriptorHeap** outDescriptorHeap, - D3D12_GPU_DESCRIPTOR_HANDLE* outGpuHandle, - ::XCEngine::UI::UIRect& outRect, - std::string* outReason) { - if (outDescriptorHeap != nullptr) { - *outDescriptorHeap = nullptr; - } - if (outGpuHandle != nullptr) { - *outGpuHandle = {}; - } - outRect = {}; - if (outReason != nullptr) { - outReason->clear(); - } - - if (!EnsureTextRasterizer()) { - if (outReason != nullptr) { - *outReason = m_lastError; - } - return false; - } - if (!m_uploadedTextureAllocator.IsInitialized()) { - if (outReason != nullptr) { - *outReason = "native uploaded-texture descriptor allocator is not initialized."; - } - return false; - } - if (command.text.empty()) { - if (outReason != nullptr) { - *outReason = "text command is empty."; - } - return false; - } - - const float dpiScale = ClampDpiScale(m_dpiScale); - const float scaledFontSize = ResolveFontSize(command.fontSize) * dpiScale; - const int fontKey = static_cast(std::lround(scaledFontSize * 10.0f)); - const std::string cacheKey = - std::to_string(fontKey) + "|" + command.text; - - auto cacheEntry = m_cachedTextTextures.find(cacheKey); - if (cacheEntry == m_cachedTextTextures.end()) { - IDWriteTextFormat* textFormat = GetTextFormat(scaledFontSize); - if (textFormat == nullptr) { - if (outReason != nullptr) { - *outReason = "native text cache could not create a DWrite text format."; - } - return false; - } - - const std::wstring wideText = Utf8ToWide(command.text); - if (wideText.empty()) { - if (outReason != nullptr) { - *outReason = "native text cache could not convert UTF-8 text to UTF-16."; - } - return false; - } - - ComPtr textLayout = {}; - const float lineHeight = std::ceil(scaledFontSize * 1.6f); - HRESULT hr = m_textDWriteFactory->CreateTextLayout( - wideText.c_str(), - static_cast(wideText.size()), - textFormat, - 4096.0f, - lineHeight, - textLayout.ReleaseAndGetAddressOf()); - if (FAILED(hr) || textLayout == nullptr) { - if (outReason != nullptr) { - *outReason = "native text cache could not create a text layout."; - } - return false; - } - - DWRITE_TEXT_METRICS textMetrics = {}; - hr = textLayout->GetMetrics(&textMetrics); - if (FAILED(hr)) { - if (outReason != nullptr) { - *outReason = "native text cache could not query text metrics."; - } - return false; - } - - const UINT widthPx = static_cast((std::max)( - 1.0f, - std::ceil(textMetrics.widthIncludingTrailingWhitespace) + 2.0f)); - const UINT heightPx = static_cast((std::max)( - 1.0f, - std::ceil(lineHeight) + 2.0f)); - - ComPtr bitmap = {}; - hr = m_textWicFactory->CreateBitmap( - widthPx, - heightPx, - GUID_WICPixelFormat32bppPBGRA, - WICBitmapCacheOnLoad, - bitmap.ReleaseAndGetAddressOf()); - if (FAILED(hr) || bitmap == nullptr) { - if (outReason != nullptr) { - *outReason = "native text cache could not create a WIC bitmap."; - } - return false; - } - - const D2D1_RENDER_TARGET_PROPERTIES renderTargetProperties = - D2D1::RenderTargetProperties( - D2D1_RENDER_TARGET_TYPE_DEFAULT, - D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED), - kBaseDpi, - kBaseDpi); - - ComPtr renderTarget = {}; - hr = m_textD2DFactory->CreateWicBitmapRenderTarget( - bitmap.Get(), - renderTargetProperties, - renderTarget.ReleaseAndGetAddressOf()); - if (FAILED(hr) || renderTarget == nullptr) { - if (outReason != nullptr) { - *outReason = "native text cache could not create a WIC bitmap render target."; - } - return false; - } - - ComPtr brush = {}; - hr = renderTarget->CreateSolidColorBrush( - D2D1::ColorF(1.0f, 1.0f, 1.0f, 1.0f), - brush.ReleaseAndGetAddressOf()); - if (FAILED(hr) || brush == nullptr) { - if (outReason != nullptr) { - *outReason = "native text cache could not create a text brush."; - } - return false; - } - - renderTarget->SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE); - renderTarget->BeginDraw(); - renderTarget->Clear(D2D1::ColorF(0.0f, 0.0f, 0.0f, 0.0f)); - renderTarget->DrawTextW( - wideText.c_str(), - static_cast(wideText.size()), - textFormat, - D2D1::RectF(0.0f, 0.0f, static_cast(widthPx), static_cast(heightPx)), - brush.Get(), - D2D1_DRAW_TEXT_OPTIONS_CLIP, - DWRITE_MEASURING_MODE_GDI_NATURAL); - hr = renderTarget->EndDraw(); - if (FAILED(hr)) { - if (outReason != nullptr) { - *outReason = "native text cache could not rasterize a text bitmap."; - } - return false; - } - - std::vector bgraPixels( - static_cast(widthPx) * static_cast(heightPx) * 4u); - hr = bitmap->CopyPixels( - nullptr, - widthPx * 4u, - static_cast(bgraPixels.size()), - bgraPixels.data()); - if (FAILED(hr)) { - if (outReason != nullptr) { - *outReason = "native text cache could not read back text bitmap pixels."; - } - return false; - } - - const std::vector rgbaPixels = - ConvertPremultipliedBgraToPremultipliedRgba( - bgraPixels.data(), - widthPx, - heightPx); - - ::XCEngine::RHI::TextureDesc textureDesc = {}; - textureDesc.width = widthPx; - textureDesc.height = heightPx; - textureDesc.depth = 1u; - textureDesc.mipLevels = 1u; - textureDesc.arraySize = 1u; - textureDesc.format = - static_cast(::XCEngine::RHI::Format::R8G8B8A8_UNorm); - textureDesc.textureType = - static_cast(::XCEngine::RHI::TextureType::Texture2D); - textureDesc.sampleCount = 1u; - textureDesc.sampleQuality = 0u; - textureDesc.flags = 0u; - - CachedTextTextureEntry newEntry = {}; - newEntry.uploaded.texture = m_windowRenderer->GetRHIDevice()->CreateTexture( - textureDesc, - rgbaPixels.data(), - rgbaPixels.size(), - widthPx * 4u); - if (newEntry.uploaded.texture == nullptr) { - if (outReason != nullptr) { - *outReason = "native text cache could not upload a text bitmap to the GPU."; - } - return false; - } - - if (!m_uploadedTextureAllocator.CreateTextureDescriptor( - newEntry.uploaded.texture, - &newEntry.uploaded.cpuHandle, - &newEntry.uploaded.gpuHandle)) { - ReleaseUploadedTexture(newEntry.uploaded); - if (outReason != nullptr) { - *outReason = "native text cache could not allocate a GPU descriptor for a text bitmap."; - } - return false; - } - - newEntry.uploaded.width = widthPx; - newEntry.uploaded.height = heightPx; - newEntry.widthDips = static_cast(widthPx) / dpiScale; - newEntry.heightDips = static_cast(heightPx) / dpiScale; - - cacheEntry = m_cachedTextTextures.emplace(cacheKey, std::move(newEntry)).first; - } - - if (outDescriptorHeap != nullptr) { - *outDescriptorHeap = m_uploadedTextureAllocator.GetDescriptorHeap(); - } - if (outGpuHandle != nullptr) { - *outGpuHandle = cacheEntry->second.uploaded.gpuHandle; - } - outRect = ::XCEngine::UI::UIRect( - command.position.x, - command.position.y, - cacheEntry->second.widthDips, - cacheEntry->second.heightDips); - return true; -} - -bool D3D12UIRenderer::RenderSupportedFrame(const UIDrawData& drawData) { - if (m_windowRenderer == nullptr) { - m_lastError = "Native D3D12 UI renderer requires an attached window renderer."; - return false; - } - - const ::XCEngine::Rendering::RenderContext renderContext = - m_windowRenderer->GetRenderContext(); - const ::XCEngine::Rendering::RenderSurface* targetSurface = - m_windowRenderer->GetCurrentRenderSurface(); - auto* nativeCommandList = - renderContext.commandList != nullptr - ? static_cast<::XCEngine::RHI::D3D12CommandList*>(renderContext.commandList) - : nullptr; - if (!renderContext.IsValid() || - renderContext.commandList == nullptr || - nativeCommandList == nullptr || - targetSurface == nullptr || - !::XCEngine::Rendering::HasSingleColorAttachment(*targetSurface)) { - m_lastError = "Native D3D12 UI renderer could not resolve the current frame render target."; - return false; - } - - if (drawData.Empty()) { - if (!m_windowRenderer->SubmitFrame(false)) { - m_lastError = "Native D3D12 UI renderer failed to submit an empty frame: " + - m_windowRenderer->GetLastError(); - return false; - } - if (!m_windowRenderer->SignalFrameCompletion()) { - m_lastError = "Native D3D12 UI renderer failed to signal an empty frame."; - return false; - } - if (!m_windowRenderer->PresentFrame()) { - m_lastError = "Native D3D12 UI renderer failed to present an empty frame."; - return false; - } - - m_lastError.clear(); - return true; - } - - if (!EnsureShapePassResources(*targetSurface) || - ((m_lastFrameAnalysis.requiresImages || m_lastFrameAnalysis.requiresText) && - !EnsureImagePassResources(*targetSurface))) { - return false; - } - - std::size_t requiredDrawCount = 0u; - for (const UIDrawList& drawList : drawData.GetDrawLists()) { - for (const UIDrawCommand& command : drawList.GetCommands()) { - switch (command.type) { - case UIDrawCommandType::FilledRect: - case UIDrawCommandType::RectOutline: - case UIDrawCommandType::Image: - case UIDrawCommandType::Text: - ++requiredDrawCount; - break; - case UIDrawCommandType::Line: { - ::XCEngine::UI::UIRect lineRect = {}; - if (TryBuildAxisAlignedLineRect(command, lineRect)) { - ++requiredDrawCount; - } - break; - } - default: - break; - } - } - } - - const std::uint32_t frameSlot = m_windowRenderer->GetActiveFrameSlot(); - if (!EnsureFrameConstantBufferCapacity(frameSlot, requiredDrawCount)) { - return false; - } - - if (!m_windowRenderer->PreparePresentSurface()) { - m_lastError = "Native D3D12 UI renderer failed to prepare the D3D12 present surface: " + - m_windowRenderer->GetLastError(); - return false; - } - - std::vector<::XCEngine::RHI::RHIResourceView*> renderTargets = - targetSurface->GetColorAttachments(); - if (renderTargets.empty() || renderTargets[0] == nullptr) { - m_lastError = "Native D3D12 UI renderer could not resolve the swap chain render target view."; - return false; - } - - auto* commandList = renderContext.commandList; - commandList->SetRenderTargets( - static_cast(renderTargets.size()), - renderTargets.data(), - nullptr); - - ::XCEngine::RHI::Viewport viewport = {}; - viewport.topLeftX = 0.0f; - viewport.topLeftY = 0.0f; - viewport.width = static_cast(targetSurface->GetWidth()); - viewport.height = static_cast(targetSurface->GetHeight()); - viewport.minDepth = 0.0f; - viewport.maxDepth = 1.0f; - commandList->SetViewport(viewport); - commandList->SetPrimitiveTopology(::XCEngine::RHI::PrimitiveTopology::TriangleList); - - const auto* shapeLayout = - static_cast(m_shapePipelineLayout); - const auto* imageLayout = - static_cast(m_imagePipelineLayout); - const std::uint32_t shapeCbvRootIndex = - shapeLayout->GetConstantBufferRootParameterIndex(kQuadPassConstantBinding); - const std::uint32_t imageCbvRootIndex = - imageLayout != nullptr - ? imageLayout->GetConstantBufferRootParameterIndex(kQuadPassConstantBinding) - : 0u; - const std::uint32_t imageSrvRootIndex = - imageLayout != nullptr - ? imageLayout->GetShaderResourceTableRootParameterIndex() - : 0u; - const std::uint32_t imageSamplerRootIndex = - imageLayout != nullptr - ? imageLayout->GetSamplerTableRootParameterIndex() - : 0u; - const std::uint64_t alignedConstantSize = - AlignConstantBufferSize(sizeof(QuadPassConstants)); - auto* frameConstantBuffer = m_frameConstantBuffers[frameSlot]; - std::uint64_t emittedDrawCount = 0u; - const float dpiScale = ClampDpiScale(m_dpiScale); - - std::vector<::XCEngine::RHI::Rect> clipStack = {}; - const ::XCEngine::RHI::Rect fullScissor = BuildFullScissorRect( - targetSurface->GetWidth(), - targetSurface->GetHeight()); - commandList->SetScissorRect(fullScissor); - - enum class ActivePipeline : std::uint8_t { - None = 0, - Shape, - Image - }; - - ActivePipeline activePipeline = ActivePipeline::None; - ID3D12DescriptorHeap* activeTextureHeap = nullptr; - - const auto writeConstants = - [&](const QuadPassConstants& constants, std::uint32_t rootParameterIndex) { - const std::uint64_t constantOffset = emittedDrawCount * alignedConstantSize; - frameConstantBuffer->SetData( - &constants, - sizeof(constants), - static_cast(constantOffset)); - nativeCommandList->SetGraphicsRootConstantBufferView( - rootParameterIndex, - frameConstantBuffer->GetGPUVirtualAddress() + constantOffset); - ++emittedDrawCount; - }; - - const auto currentClipOrRect = - [&](const ::XCEngine::UI::UIRect& rect) { - const ::XCEngine::RHI::Rect rectPixels = ToPixelRect( - rect, - dpiScale, - targetSurface->GetWidth(), - targetSurface->GetHeight()); - return clipStack.empty() - ? rectPixels - : IntersectRects(rectPixels, clipStack.back()); - }; - - const auto emitShape = - [&](const ::XCEngine::UI::UIRect& rect, - const ::XCEngine::UI::UIColor& color, - float rounding, - float strokeThickness) { - if (rect.width <= 0.0f || rect.height <= 0.0f || color.a <= 0.0f) { - return true; - } - - const ::XCEngine::RHI::Rect activeScissor = currentClipOrRect(rect); - if (IsEmptyRect(activeScissor)) { - return true; - } - - if (activePipeline != ActivePipeline::Shape) { - commandList->SetPipelineState(m_shapePipelineState); - activePipeline = ActivePipeline::Shape; - activeTextureHeap = nullptr; - } - - const QuadPassConstants constants = BuildQuadPassConstants( - rect, - color, - dpiScale, - targetSurface->GetWidth(), - targetSurface->GetHeight(), - rounding, - strokeThickness); - writeConstants(constants, shapeCbvRootIndex); - commandList->SetScissorRect(activeScissor); - commandList->Draw(6u); - return true; - }; - - const auto emitImage = - [&](const ::XCEngine::UI::UIRect& rect, - const ::XCEngine::UI::UIColor& tintColor, - const ::XCEngine::UI::UIPoint& uvMin, - const ::XCEngine::UI::UIPoint& uvMax, - ID3D12DescriptorHeap* textureHeap, - D3D12_GPU_DESCRIPTOR_HANDLE textureHandle, - const char* failureLabel) { - if (rect.width <= 0.0f || rect.height <= 0.0f) { - return true; - } - - const ::XCEngine::RHI::Rect activeScissor = currentClipOrRect(rect); - if (IsEmptyRect(activeScissor)) { - return true; - } - - if (textureHeap == nullptr || textureHandle.ptr == 0u || m_imageSamplerHeap == nullptr) { - m_lastError = failureLabel; - return false; - } - - if (activePipeline != ActivePipeline::Image) { - commandList->SetPipelineState(m_imagePipelineState); - activePipeline = ActivePipeline::Image; - activeTextureHeap = nullptr; - } - - if (activeTextureHeap != textureHeap) { - ID3D12DescriptorHeap* heaps[] = { - textureHeap, - m_imageSamplerHeap->GetDescriptorHeap() - }; - nativeCommandList->SetDescriptorHeaps(2u, heaps); - nativeCommandList->SetGraphicsRootDescriptorTable( - imageSamplerRootIndex, - m_imageSamplerGpuHandle); - activeTextureHeap = textureHeap; - } - - const QuadPassConstants constants = BuildQuadPassConstants( - rect, - tintColor, - dpiScale, - targetSurface->GetWidth(), - targetSurface->GetHeight(), - 0.0f, - 0.0f, - uvMin, - uvMax); - writeConstants(constants, imageCbvRootIndex); - nativeCommandList->SetGraphicsRootDescriptorTable( - imageSrvRootIndex, - textureHandle); - commandList->SetScissorRect(activeScissor); - commandList->Draw(6u); - return true; - }; - - for (const UIDrawList& drawList : drawData.GetDrawLists()) { - for (const UIDrawCommand& command : drawList.GetCommands()) { - switch (command.type) { - case UIDrawCommandType::FilledRect: - if (!emitShape(command.rect, command.color, command.rounding, 0.0f)) { - return false; - } - break; - case UIDrawCommandType::RectOutline: - if (!emitShape(command.rect, command.color, command.rounding, command.thickness)) { - return false; - } - break; - case UIDrawCommandType::Line: { - ::XCEngine::UI::UIRect lineRect = {}; - if (TryBuildAxisAlignedLineRect(command, lineRect) && - !emitShape(lineRect, command.color, 0.0f, 0.0f)) { - return false; - } - break; - } - case UIDrawCommandType::Image: { - ID3D12DescriptorHeap* descriptorHeap = nullptr; - D3D12_GPU_DESCRIPTOR_HANDLE descriptorHandle = {}; - std::string imageReason = {}; - if (!ResolveUploadedTextureBinding( - command.texture, - &descriptorHeap, - &descriptorHandle, - &imageReason)) { - m_lastError = imageReason; - return false; - } - if (!emitImage( - command.rect, - command.color, - command.uvMin, - command.uvMax, - descriptorHeap, - descriptorHandle, - "native D3D12 UI renderer failed to bind an image texture.")) { - return false; - } - break; - } - case UIDrawCommandType::Text: { - ID3D12DescriptorHeap* descriptorHeap = nullptr; - D3D12_GPU_DESCRIPTOR_HANDLE descriptorHandle = {}; - ::XCEngine::UI::UIRect textRect = {}; - std::string textReason = {}; - if (!ResolveTextTextureBinding( - command, - &descriptorHeap, - &descriptorHandle, - textRect, - &textReason)) { - m_lastError = textReason; - return false; - } - if (!emitImage( - textRect, - command.color, - {}, - ::XCEngine::UI::UIPoint(1.0f, 1.0f), - descriptorHeap, - descriptorHandle, - "native D3D12 UI renderer failed to bind a text texture.")) { - return false; - } - break; - } - case UIDrawCommandType::PushClipRect: { - ::XCEngine::RHI::Rect clipRect = ToPixelRect( - command.rect, - dpiScale, - targetSurface->GetWidth(), - targetSurface->GetHeight()); - if (command.intersectWithCurrentClip && !clipStack.empty()) { - clipRect = IntersectRects(clipRect, clipStack.back()); - } - clipStack.push_back(clipRect); - break; - } - case UIDrawCommandType::PopClipRect: - if (!clipStack.empty()) { - clipStack.pop_back(); - } - break; - default: - break; - } - } - } - - commandList->TransitionBarrier( - renderTargets[0], - ::XCEngine::RHI::ResourceStates::RenderTarget, - ::XCEngine::RHI::ResourceStates::Present); - - if (!m_windowRenderer->SubmitFrame(false)) { - m_lastError = "Native D3D12 UI renderer failed to submit the frame: " + - m_windowRenderer->GetLastError(); - return false; - } - if (!m_windowRenderer->SignalFrameCompletion()) { - m_lastError = "Native D3D12 UI renderer failed to signal frame completion."; - return false; - } - if (!m_windowRenderer->PresentFrame()) { - m_lastError = "Native D3D12 UI renderer failed to present the swap chain."; - return false; - } - - m_lastError.clear(); - return true; -} - -D3D12UIRendererFrameAnalysis D3D12UIRenderer::BuildFrameAnalysis(const UIDrawData& drawData) { - D3D12UIRendererFrameAnalysis analysis = {}; - analysis.drawListCount = drawData.GetDrawListCount(); - analysis.commandCount = drawData.GetTotalCommandCount(); - analysis.empty = analysis.commandCount == 0u; - - for (const UIDrawList& drawList : drawData.GetDrawLists()) { - for (const UIDrawCommand& command : drawList.GetCommands()) { - switch (command.type) { - case UIDrawCommandType::FilledRect: - ++analysis.filledRectCommandCount; - break; - case UIDrawCommandType::RectOutline: - ++analysis.outlineCommandCount; - break; - case UIDrawCommandType::FilledRectLinearGradient: - ++analysis.gradientCommandCount; - analysis.requiresGradients = true; - break; - case UIDrawCommandType::Line: - ++analysis.lineCommandCount; - break; - case UIDrawCommandType::FilledTriangle: - case UIDrawCommandType::FilledCircle: - case UIDrawCommandType::CircleOutline: - ++analysis.geometryCommandCount; - break; - case UIDrawCommandType::Text: - ++analysis.textCommandCount; - analysis.requiresText = true; - break; - case UIDrawCommandType::Image: - ++analysis.imageCommandCount; - analysis.requiresImages = true; - break; - case UIDrawCommandType::PushClipRect: - case UIDrawCommandType::PopClipRect: - ++analysis.clipCommandCount; - analysis.requiresClipStack = true; - break; - default: - break; - } - } - } - - return analysis; -} - -} // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Rendering/D3D12/D3D12UIRenderer.h b/new_editor/app/Rendering/D3D12/D3D12UIRenderer.h deleted file mode 100644 index 72beda97..00000000 --- a/new_editor/app/Rendering/D3D12/D3D12UIRenderer.h +++ /dev/null @@ -1,143 +0,0 @@ -#pragma once - -#include "D3D12ShaderResourceDescriptorAllocator.h" -#include "D3D12WindowRenderer.h" - -#include "Ports/TextureDataPort.h" - -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -namespace XCEngine::Rendering { -class RenderSurface; -} - -namespace XCEngine::RHI { -class D3D12Buffer; -class D3D12DescriptorHeap; -class RHIPipelineLayout; -class RHIPipelineState; -class RHITexture; -} - -namespace XCEngine::UI::Editor::Host { - -struct D3D12UIRendererFrameAnalysis { - std::size_t drawListCount = 0u; - std::size_t commandCount = 0u; - std::uint32_t filledRectCommandCount = 0u; - std::uint32_t outlineCommandCount = 0u; - std::uint32_t gradientCommandCount = 0u; - std::uint32_t lineCommandCount = 0u; - std::uint32_t geometryCommandCount = 0u; - std::uint32_t textCommandCount = 0u; - std::uint32_t imageCommandCount = 0u; - std::uint32_t clipCommandCount = 0u; - bool requiresText = false; - bool requiresImages = false; - bool requiresGradients = false; - bool requiresClipStack = false; - bool empty = true; -}; - -class D3D12UIRenderer { -public: - bool AttachWindowRenderer(D3D12WindowRenderer& windowRenderer); - void DetachWindowRenderer(); - void SetTextureDataSource(const Ports::TextureDataPort* textureDataSource); - - bool HasAttachedWindowRenderer() const; - void SetDpiScale(float dpiScale); - float GetDpiScale() const; - void AnalyzeFrame(const ::XCEngine::UI::UIDrawData& drawData); - bool CanRender(const ::XCEngine::UI::UIDrawData& drawData, std::string* outReason = nullptr); - bool Render(const ::XCEngine::UI::UIDrawData& drawData); - - const D3D12UIRendererFrameAnalysis& GetLastFrameAnalysis() const; - const std::string& GetLastError() const; - -private: - struct UploadedTextureCacheEntry { - ::XCEngine::RHI::RHITexture* texture = nullptr; - D3D12_CPU_DESCRIPTOR_HANDLE cpuHandle = {}; - D3D12_GPU_DESCRIPTOR_HANDLE gpuHandle = {}; - const std::uint8_t* sourcePixels = nullptr; - std::uint32_t width = 0u; - std::uint32_t height = 0u; - }; - - struct CachedTextTextureEntry { - UploadedTextureCacheEntry uploaded = {}; - float widthDips = 0.0f; - float heightDips = 0.0f; - }; - - static D3D12UIRendererFrameAnalysis BuildFrameAnalysis( - const ::XCEngine::UI::UIDrawData& drawData); - void ReleaseResources(); - void ReleasePipelineResources(); - void ReleaseFrameConstantBuffers(); - void ReleaseUploadedTextureCaches(); - void ReleaseUploadedTexture(UploadedTextureCacheEntry& texture); - void ReleaseTextRasterizer(); - bool EnsureShapePassResources(const ::XCEngine::Rendering::RenderSurface& targetSurface); - bool EnsureImagePassResources(const ::XCEngine::Rendering::RenderSurface& targetSurface); - bool EnsureFrameConstantBufferCapacity( - std::uint32_t frameSlot, - std::size_t requiredDrawCount); - bool EnsureTextRasterizer(); - IDWriteTextFormat* GetTextFormat(float scaledFontSize); - bool ValidateSupportedFrame( - const ::XCEngine::UI::UIDrawData& drawData, - std::string* outReason); - bool RenderSupportedFrame(const ::XCEngine::UI::UIDrawData& drawData); - bool ResolveUploadedTextureBinding( - const ::XCEngine::UI::UITextureHandle& texture, - ID3D12DescriptorHeap** outDescriptorHeap, - D3D12_GPU_DESCRIPTOR_HANDLE* outGpuHandle, - std::string* outReason); - bool ResolveTextTextureBinding( - const ::XCEngine::UI::UIDrawCommand& command, - ID3D12DescriptorHeap** outDescriptorHeap, - D3D12_GPU_DESCRIPTOR_HANDLE* outGpuHandle, - ::XCEngine::UI::UIRect& outRect, - std::string* outReason); - - D3D12WindowRenderer* m_windowRenderer = nullptr; - const Ports::TextureDataPort* m_textureDataSource = nullptr; - D3D12UIRendererFrameAnalysis m_lastFrameAnalysis = {}; - std::string m_lastError = {}; - ::XCEngine::RHI::RHIPipelineLayout* m_shapePipelineLayout = nullptr; - ::XCEngine::RHI::RHIPipelineState* m_shapePipelineState = nullptr; - ::XCEngine::RHI::RHIPipelineLayout* m_imagePipelineLayout = nullptr; - ::XCEngine::RHI::RHIPipelineState* m_imagePipelineState = nullptr; - std::vector<::XCEngine::RHI::D3D12Buffer*> m_frameConstantBuffers = {}; - std::vector m_frameConstantBufferCapacities = {}; - D3D12ShaderResourceDescriptorAllocator m_uploadedTextureAllocator = {}; - ::XCEngine::RHI::D3D12DescriptorHeap* m_imageSamplerHeap = nullptr; - D3D12_CPU_DESCRIPTOR_HANDLE m_imageSamplerCpuHandle = {}; - D3D12_GPU_DESCRIPTOR_HANDLE m_imageSamplerGpuHandle = {}; - std::unordered_map m_uploadedTextureCache = {}; - std::unordered_map m_cachedTextTextures = {}; - Microsoft::WRL::ComPtr m_textD2DFactory = {}; - Microsoft::WRL::ComPtr m_textDWriteFactory = {}; - Microsoft::WRL::ComPtr m_textWicFactory = {}; - std::unordered_map> m_textFormats = {}; - bool m_textComInitialized = false; - std::uint32_t m_cachedRenderTargetFormat = 0u; - std::uint32_t m_cachedSampleCount = 0u; - std::uint32_t m_cachedSampleQuality = 0u; - float m_dpiScale = 1.0f; -}; - -} // namespace XCEngine::UI::Editor::Host diff --git a/new_editor/app/Rendering/D3D12/D3D12WindowInteropContext.cpp b/new_editor/app/Rendering/D3D12/D3D12WindowInteropContext.cpp index 8ac43367..9ba1fb86 100644 --- a/new_editor/app/Rendering/D3D12/D3D12WindowInteropContext.cpp +++ b/new_editor/app/Rendering/D3D12/D3D12WindowInteropContext.cpp @@ -1,5 +1,4 @@ #include "D3D12WindowInteropHelpers.h" -#include "Support/EnvironmentFlags.h" #include #include @@ -53,17 +52,6 @@ namespace XCEngine::UI::Editor::Host { using namespace D3D12WindowInteropHelpers; -#ifdef _DEBUG -namespace { - -bool ShouldEnableD3D11InteropDebug() { - return App::IsEnvironmentFlagEnabled("XCUIEDITOR_ENABLE_GPU_DEBUG") || - App::IsEnvironmentFlagEnabled("XCUIEDITOR_ENABLE_GPU_VALIDATION"); -} - -} // namespace -#endif - bool D3D12WindowInteropContext::Attach( D3D12WindowRenderer& windowRenderer, ID2D1Factory1& d2dFactory) { @@ -151,9 +139,7 @@ bool D3D12WindowInteropContext::EnsureInterop() { UINT createFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT; #ifdef _DEBUG - if (ShouldEnableD3D11InteropDebug()) { - createFlags |= D3D11_CREATE_DEVICE_DEBUG; - } + createFlags |= D3D11_CREATE_DEVICE_DEBUG; #endif D3D_FEATURE_LEVEL actualFeatureLevel = D3D_FEATURE_LEVEL_11_0; @@ -169,8 +155,7 @@ bool D3D12WindowInteropContext::EnsureInterop() { m_d3d11DeviceContext.ReleaseAndGetAddressOf(), &actualFeatureLevel); #ifdef _DEBUG - if (FAILED(hr) && - (createFlags & D3D11_CREATE_DEVICE_DEBUG) != 0u) { + if (FAILED(hr)) { createFlags &= ~D3D11_CREATE_DEVICE_DEBUG; hr = D3D11On12CreateDevice( d3d12Device, diff --git a/new_editor/app/Rendering/D3D12/D3D12WindowRenderLoop.cpp b/new_editor/app/Rendering/D3D12/D3D12WindowRenderLoop.cpp index 576fe03e..04e09832 100644 --- a/new_editor/app/Rendering/D3D12/D3D12WindowRenderLoop.cpp +++ b/new_editor/app/Rendering/D3D12/D3D12WindowRenderLoop.cpp @@ -7,9 +7,6 @@ D3D12WindowRenderLoopAttachResult D3D12WindowRenderLoop::Attach( D3D12WindowRenderer& windowRenderer) { m_uiRenderer = &uiRenderer; m_windowRenderer = &windowRenderer; - m_nativeUiRenderer.AttachWindowRenderer(windowRenderer); - m_nativeUiRenderer.SetTextureDataSource(&uiRenderer); - m_nativeUiRenderer.SetDpiScale(uiRenderer.GetDpiScale()); D3D12WindowRenderLoopAttachResult result = {}; result.hasViewportSurfacePresentation = m_uiRenderer->AttachWindowRenderer(*m_windowRenderer); @@ -28,16 +25,10 @@ void D3D12WindowRenderLoop::Detach() { m_uiRenderer->DetachWindowRenderer(); } - m_nativeUiRenderer.SetTextureDataSource(nullptr); - m_nativeUiRenderer.DetachWindowRenderer(); m_uiRenderer = nullptr; m_windowRenderer = nullptr; } -void D3D12WindowRenderLoop::SetDpiScale(float dpiScale) { - m_nativeUiRenderer.SetDpiScale(dpiScale); -} - D3D12WindowRenderLoopFrameContext D3D12WindowRenderLoop::BeginFrame() const { D3D12WindowRenderLoopFrameContext context = {}; if (!HasViewportSurfacePresentation()) { @@ -113,28 +104,14 @@ D3D12WindowRenderLoopResizeResult D3D12WindowRenderLoop::ApplyResize(UINT width, } D3D12WindowRenderLoopPresentResult D3D12WindowRenderLoop::Present( - const ::XCEngine::UI::UIDrawData& drawData) { + const ::XCEngine::UI::UIDrawData& drawData) const { D3D12WindowRenderLoopPresentResult result = {}; if (m_uiRenderer == nullptr) { result.warning = "window render loop has no ui renderer."; return result; } - m_nativeUiRenderer.AnalyzeFrame(drawData); - if (HasViewportSurfacePresentation()) { - std::string nativeUnsupportedReason = {}; - if (m_nativeUiRenderer.CanRender(drawData, &nativeUnsupportedReason)) { - result.framePresented = m_nativeUiRenderer.Render(drawData); - if (!result.framePresented) { - const std::string& nativeError = m_nativeUiRenderer.GetLastError(); - result.warning = nativeError.empty() - ? "native d3d12 ui rendering failed." - : "native d3d12 ui rendering failed: " + nativeError; - } - return result; - } - result.framePresented = m_uiRenderer->RenderToWindowRenderer(drawData); if (!result.framePresented) { const std::string& composeError = m_uiRenderer->GetLastRenderError(); diff --git a/new_editor/app/Rendering/D3D12/D3D12WindowRenderLoop.h b/new_editor/app/Rendering/D3D12/D3D12WindowRenderLoop.h index 4984a55a..f8dd4655 100644 --- a/new_editor/app/Rendering/D3D12/D3D12WindowRenderLoop.h +++ b/new_editor/app/Rendering/D3D12/D3D12WindowRenderLoop.h @@ -1,6 +1,5 @@ #pragma once -#include #include #include @@ -38,17 +37,15 @@ public: NativeRenderer& uiRenderer, D3D12WindowRenderer& windowRenderer); void Detach(); - void SetDpiScale(float dpiScale); D3D12WindowRenderLoopFrameContext BeginFrame() const; D3D12WindowRenderLoopResizeResult ApplyResize(UINT width, UINT height); D3D12WindowRenderLoopPresentResult Present( - const ::XCEngine::UI::UIDrawData& drawData); + const ::XCEngine::UI::UIDrawData& drawData) const; bool HasViewportSurfacePresentation() const; private: - D3D12UIRenderer m_nativeUiRenderer = {}; NativeRenderer* m_uiRenderer = nullptr; D3D12WindowRenderer* m_windowRenderer = nullptr; }; diff --git a/new_editor/app/Rendering/D3D12/D3D12WindowRenderer.cpp b/new_editor/app/Rendering/D3D12/D3D12WindowRenderer.cpp index b4376a1f..e785bbf0 100644 --- a/new_editor/app/Rendering/D3D12/D3D12WindowRenderer.cpp +++ b/new_editor/app/Rendering/D3D12/D3D12WindowRenderer.cpp @@ -219,14 +219,6 @@ std::uint32_t D3D12WindowRenderer::GetBackBufferCount() const { return m_presenter.GetBackBufferCount(); } -std::uint32_t D3D12WindowRenderer::GetActiveFrameSlot() const { - return m_activeFrameSlot; -} - -ID3D12DescriptorHeap* D3D12WindowRenderer::GetViewportTextureDescriptorHeap() const { - return m_viewportTextureAllocator.GetDescriptorHeap(); -} - ::XCEngine::Rendering::RenderContext D3D12WindowRenderer::GetRenderContext() const { return m_hostDevice.GetRenderContext(m_activeFrameSlot); } diff --git a/new_editor/app/Rendering/D3D12/D3D12WindowRenderer.h b/new_editor/app/Rendering/D3D12/D3D12WindowRenderer.h index a2d3c63f..6fc7997a 100644 --- a/new_editor/app/Rendering/D3D12/D3D12WindowRenderer.h +++ b/new_editor/app/Rendering/D3D12/D3D12WindowRenderer.h @@ -53,8 +53,6 @@ public: const ::XCEngine::RHI::D3D12Texture* GetCurrentBackBufferTexture() const; const ::XCEngine::RHI::D3D12Texture* GetBackBufferTexture(std::uint32_t index) const; std::uint32_t GetBackBufferCount() const; - std::uint32_t GetActiveFrameSlot() const; - ID3D12DescriptorHeap* GetViewportTextureDescriptorHeap() const; ::XCEngine::Rendering::RenderContext GetRenderContext() const; private: diff --git a/new_editor/app/Rendering/Native/NativeRenderer.cpp b/new_editor/app/Rendering/Native/NativeRenderer.cpp index 46ed48f6..620295ae 100644 --- a/new_editor/app/Rendering/Native/NativeRenderer.cpp +++ b/new_editor/app/Rendering/Native/NativeRenderer.cpp @@ -1,6 +1,4 @@ #include "NativeRendererHelpers.h" -#include "Support/EnvironmentFlags.h" - #include #include #include @@ -11,17 +9,6 @@ namespace XCEngine::UI::Editor::Host { using namespace NativeRendererHelpers; -#ifdef _DEBUG -namespace { - -bool ShouldEnableD2DDebugLayer() { - return App::IsEnvironmentFlagEnabled("XCUIEDITOR_ENABLE_GPU_DEBUG") || - App::IsEnvironmentFlagEnabled("XCUIEDITOR_ENABLE_GPU_VALIDATION"); -} - -} // namespace -#endif - bool NativeRenderer::Initialize(HWND hwnd) { Shutdown(); @@ -33,9 +20,7 @@ bool NativeRenderer::Initialize(HWND hwnd) { m_hwnd = hwnd; D2D1_FACTORY_OPTIONS factoryOptions = {}; #ifdef _DEBUG - if (ShouldEnableD2DDebugLayer()) { - factoryOptions.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION; - } + factoryOptions.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION; #endif HRESULT hr = D2D1CreateFactory( D2D1_FACTORY_TYPE_SINGLE_THREADED, @@ -522,16 +507,9 @@ void NativeRenderer::RenderRectOutlineCommand( ID2D1RenderTarget& renderTarget, ID2D1SolidColorBrush& solidBrush, const ::XCEngine::UI::UIDrawCommand& command) { - if (command.thickness <= 0.0f || - command.color.a <= 0.0f || - command.rect.width <= 0.0f || - command.rect.height <= 0.0f) { - return; - } - const float dpiScale = ClampDpiScale(m_dpiScale); const D2D1_RECT_F rect = ToD2DRect(command.rect, dpiScale); - const float thickness = command.thickness * dpiScale; + const float thickness = (command.thickness > 0.0f ? command.thickness : 1.0f) * dpiScale; const float rounding = command.rounding > 0.0f ? command.rounding * dpiScale : 0.0f; if (command.rounding > 0.0f) { renderTarget.DrawRoundedRectangle( @@ -1093,30 +1071,6 @@ void NativeRenderer::ReleaseTexture(::XCEngine::UI::UITextureHandle& texture) { texture = {}; } -bool NativeRenderer::ResolveTexturePixelData( - const ::XCEngine::UI::UITextureHandle& texture, - Ports::TexturePixelDataView& outView) const { - outView = {}; - if (!texture.IsValid() || - texture.kind != ::XCEngine::UI::UITextureHandleKind::DescriptorHandle) { - return false; - } - - auto* resource = reinterpret_cast(texture.nativeHandle); - if (resource == nullptr || - m_liveTextures.find(resource) == m_liveTextures.end() || - resource->pixels.empty() || - resource->width == 0u || - resource->height == 0u) { - return false; - } - - outView.pixels = resource->pixels.data(); - outView.width = resource->width; - outView.height = resource->height; - return true; -} - bool NativeRenderer::ResolveTextureBitmap( ID2D1RenderTarget& renderTarget, NativeTextureResource& texture, diff --git a/new_editor/app/Rendering/Native/NativeRenderer.h b/new_editor/app/Rendering/Native/NativeRenderer.h index 5e913cd5..623f439f 100644 --- a/new_editor/app/Rendering/Native/NativeRenderer.h +++ b/new_editor/app/Rendering/Native/NativeRenderer.h @@ -4,7 +4,6 @@ #define NOMINMAX #endif -#include "Ports/TextureDataPort.h" #include "Ports/TexturePort.h" #include @@ -34,7 +33,6 @@ namespace XCEngine::UI::Editor::Host { class NativeRenderer : public Ports::TexturePort - , public Ports::TextureDataPort , public ::XCEngine::UI::Editor::UIEditorTextMeasurer { public: bool Initialize(HWND hwnd); @@ -66,9 +64,6 @@ public: ::XCEngine::UI::UITextureHandle& outTexture, std::string& outError) override; void ReleaseTexture(::XCEngine::UI::UITextureHandle& texture) override; - bool ResolveTexturePixelData( - const ::XCEngine::UI::UITextureHandle& texture, - Ports::TexturePixelDataView& outView) const override; float MeasureTextWidth( const ::XCEngine::UI::Editor::UIEditorTextMeasureRequest& request) const override; bool CaptureToPng( diff --git a/new_editor/include/XCEditor/Fields/UIEditorAssetField.h b/new_editor/include/XCEditor/Fields/UIEditorAssetField.h index 82bde529..cf88b10b 100644 --- a/new_editor/include/XCEditor/Fields/UIEditorAssetField.h +++ b/new_editor/include/XCEditor/Fields/UIEditorAssetField.h @@ -40,7 +40,6 @@ struct UIEditorAssetFieldMetrics { float horizontalPadding = 12.0f; float labelControlGap = 20.0f; float controlColumnStart = 236.0f; - float sharedControlColumnMinWidth = 0.0f; float controlTrailingInset = 8.0f; float valueBoxMinWidth = 116.0f; float controlInsetY = 1.0f; diff --git a/new_editor/include/XCEditor/Fields/UIEditorBoolField.h b/new_editor/include/XCEditor/Fields/UIEditorBoolField.h index 9daa1cd1..390173ab 100644 --- a/new_editor/include/XCEditor/Fields/UIEditorBoolField.h +++ b/new_editor/include/XCEditor/Fields/UIEditorBoolField.h @@ -31,7 +31,6 @@ struct UIEditorBoolFieldMetrics { float horizontalPadding = 12.0f; float labelControlGap = 20.0f; float controlColumnStart = 236.0f; - float sharedControlColumnMinWidth = 0.0f; float controlTrailingInset = 8.0f; float checkboxSize = 18.0f; float labelTextInsetY = 0.0f; diff --git a/new_editor/include/XCEditor/Fields/UIEditorColorField.h b/new_editor/include/XCEditor/Fields/UIEditorColorField.h index a0c6b653..cd7ec6f6 100644 --- a/new_editor/include/XCEditor/Fields/UIEditorColorField.h +++ b/new_editor/include/XCEditor/Fields/UIEditorColorField.h @@ -44,7 +44,6 @@ struct UIEditorColorFieldMetrics { float horizontalPadding = 12.0f; float labelControlGap = 20.0f; float controlColumnStart = 236.0f; - float sharedControlColumnMinWidth = 0.0f; float controlTrailingInset = 8.0f; float swatchWidth = 54.0f; float swatchInsetY = 1.0f; diff --git a/new_editor/include/XCEditor/Fields/UIEditorEnumField.h b/new_editor/include/XCEditor/Fields/UIEditorEnumField.h index 739a1ac7..e731818f 100644 --- a/new_editor/include/XCEditor/Fields/UIEditorEnumField.h +++ b/new_editor/include/XCEditor/Fields/UIEditorEnumField.h @@ -36,7 +36,6 @@ struct UIEditorEnumFieldMetrics { float horizontalPadding = 12.0f; float labelControlGap = 20.0f; float controlColumnStart = 236.0f; - float sharedControlColumnMinWidth = 0.0f; float controlTrailingInset = 8.0f; float valueBoxMinWidth = 96.0f; float controlInsetY = 1.0f; diff --git a/new_editor/include/XCEditor/Fields/UIEditorNumberField.h b/new_editor/include/XCEditor/Fields/UIEditorNumberField.h index e2104fc0..814da810 100644 --- a/new_editor/include/XCEditor/Fields/UIEditorNumberField.h +++ b/new_editor/include/XCEditor/Fields/UIEditorNumberField.h @@ -41,7 +41,6 @@ struct UIEditorNumberFieldMetrics { float horizontalPadding = 12.0f; float labelControlGap = 20.0f; float controlColumnStart = 236.0f; - float sharedControlColumnMinWidth = 0.0f; float controlTrailingInset = 8.0f; float valueBoxMinWidth = 96.0f; float controlInsetY = 1.0f; diff --git a/new_editor/include/XCEditor/Fields/UIEditorObjectField.h b/new_editor/include/XCEditor/Fields/UIEditorObjectField.h index f0f60e85..b84d32b8 100644 --- a/new_editor/include/XCEditor/Fields/UIEditorObjectField.h +++ b/new_editor/include/XCEditor/Fields/UIEditorObjectField.h @@ -38,7 +38,6 @@ struct UIEditorObjectFieldMetrics { float horizontalPadding = 12.0f; float labelControlGap = 20.0f; float controlColumnStart = 236.0f; - float sharedControlColumnMinWidth = 0.0f; float controlTrailingInset = 8.0f; float valueBoxMinWidth = 96.0f; float controlInsetY = 1.0f; diff --git a/new_editor/include/XCEditor/Fields/UIEditorPropertyGrid.h b/new_editor/include/XCEditor/Fields/UIEditorPropertyGrid.h index b15b5914..f78e3158 100644 --- a/new_editor/include/XCEditor/Fields/UIEditorPropertyGrid.h +++ b/new_editor/include/XCEditor/Fields/UIEditorPropertyGrid.h @@ -198,7 +198,6 @@ struct UIEditorPropertyGridMetrics { float horizontalPadding = 12.0f; float sectionHeaderHorizontalPadding = 6.0f; float controlColumnStart = 236.0f; - float sharedControlColumnMinWidth = 0.0f; float labelControlGap = 20.0f; float disclosureExtent = 12.0f; float disclosureLabelGap = 8.0f; diff --git a/new_editor/include/XCEditor/Fields/UIEditorTextField.h b/new_editor/include/XCEditor/Fields/UIEditorTextField.h index bf5c9b71..c4c2d4e9 100644 --- a/new_editor/include/XCEditor/Fields/UIEditorTextField.h +++ b/new_editor/include/XCEditor/Fields/UIEditorTextField.h @@ -36,7 +36,6 @@ struct UIEditorTextFieldMetrics { float horizontalPadding = 12.0f; float labelControlGap = 20.0f; float controlColumnStart = 236.0f; - float sharedControlColumnMinWidth = 0.0f; float controlTrailingInset = 8.0f; float valueBoxMinWidth = 96.0f; float controlInsetY = 1.0f; diff --git a/new_editor/include/XCEditor/Fields/UIEditorVector2Field.h b/new_editor/include/XCEditor/Fields/UIEditorVector2Field.h index 63bffcf7..2435fc77 100644 --- a/new_editor/include/XCEditor/Fields/UIEditorVector2Field.h +++ b/new_editor/include/XCEditor/Fields/UIEditorVector2Field.h @@ -47,7 +47,6 @@ struct UIEditorVector2FieldMetrics { float horizontalPadding = 12.0f; float labelControlGap = 20.0f; float controlColumnStart = 236.0f; - float sharedControlColumnMinWidth = 0.0f; float controlTrailingInset = 8.0f; float controlInsetY = 1.0f; float componentGap = 6.0f; @@ -91,6 +90,10 @@ struct UIEditorVector2FieldPalette { ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); ::XCEngine::UI::UIColor componentFocusedBorderColor = ::XCEngine::UI::UIColor(0.19f, 0.19f, 0.19f, 1.0f); + ::XCEngine::UI::UIColor prefixColor = + ::XCEngine::UI::UIColor(0.13f, 0.13f, 0.13f, 1.0f); + ::XCEngine::UI::UIColor prefixBorderColor = + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); ::XCEngine::UI::UIColor labelColor = ::XCEngine::UI::UIColor(0.72f, 0.72f, 0.72f, 1.0f); ::XCEngine::UI::UIColor valueColor = diff --git a/new_editor/include/XCEditor/Fields/UIEditorVector3Field.h b/new_editor/include/XCEditor/Fields/UIEditorVector3Field.h index 53dce5bd..aff0bafe 100644 --- a/new_editor/include/XCEditor/Fields/UIEditorVector3Field.h +++ b/new_editor/include/XCEditor/Fields/UIEditorVector3Field.h @@ -47,7 +47,6 @@ struct UIEditorVector3FieldMetrics { float horizontalPadding = 12.0f; float labelControlGap = 20.0f; float controlColumnStart = 236.0f; - float sharedControlColumnMinWidth = 0.0f; float controlTrailingInset = 8.0f; float controlInsetY = 1.0f; float componentGap = 6.0f; @@ -91,6 +90,10 @@ struct UIEditorVector3FieldPalette { ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); ::XCEngine::UI::UIColor componentFocusedBorderColor = ::XCEngine::UI::UIColor(0.19f, 0.19f, 0.19f, 1.0f); + ::XCEngine::UI::UIColor prefixColor = + ::XCEngine::UI::UIColor(0.13f, 0.13f, 0.13f, 1.0f); + ::XCEngine::UI::UIColor prefixBorderColor = + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); ::XCEngine::UI::UIColor labelColor = ::XCEngine::UI::UIColor(0.72f, 0.72f, 0.72f, 1.0f); ::XCEngine::UI::UIColor valueColor = diff --git a/new_editor/include/XCEditor/Fields/UIEditorVector4Field.h b/new_editor/include/XCEditor/Fields/UIEditorVector4Field.h index 4122af9c..8961c587 100644 --- a/new_editor/include/XCEditor/Fields/UIEditorVector4Field.h +++ b/new_editor/include/XCEditor/Fields/UIEditorVector4Field.h @@ -58,7 +58,6 @@ struct UIEditorVector4FieldMetrics { float horizontalPadding = 12.0f; float labelControlGap = 20.0f; float controlColumnStart = 236.0f; - float sharedControlColumnMinWidth = 0.0f; float controlTrailingInset = 8.0f; float controlInsetY = 1.0f; float componentGap = 6.0f; @@ -102,6 +101,10 @@ struct UIEditorVector4FieldPalette { ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); ::XCEngine::UI::UIColor componentFocusedBorderColor = ::XCEngine::UI::UIColor(0.19f, 0.19f, 0.19f, 1.0f); + ::XCEngine::UI::UIColor prefixColor = + ::XCEngine::UI::UIColor(0.13f, 0.13f, 0.13f, 1.0f); + ::XCEngine::UI::UIColor prefixBorderColor = + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); ::XCEngine::UI::UIColor labelColor = ::XCEngine::UI::UIColor(0.72f, 0.72f, 0.72f, 1.0f); ::XCEngine::UI::UIColor valueColor = diff --git a/new_editor/include/XCEditor/Shell/UIEditorShellCompose.h b/new_editor/include/XCEditor/Shell/UIEditorShellCompose.h index 1f101214..68fb8efc 100644 --- a/new_editor/include/XCEditor/Shell/UIEditorShellCompose.h +++ b/new_editor/include/XCEditor/Shell/UIEditorShellCompose.h @@ -23,12 +23,12 @@ struct UIEditorShellToolbarLayout { }; struct UIEditorShellToolbarMetrics { - float barHeight = 30.0f; + float barHeight = 24.0f; float groupPaddingX = 6.0f; float groupPaddingY = 2.0f; - float buttonWidth = 20.0f; - float buttonHeight = 20.0f; - float buttonGap = 6.0f; + float buttonWidth = 18.0f; + float buttonHeight = 16.0f; + float buttonGap = 4.0f; float groupCornerRounding = 0.0f; float buttonCornerRounding = 0.0f; float borderThickness = 1.0f; diff --git a/new_editor/include/XCEditor/Widgets/UIEditorFieldRowLayout.h b/new_editor/include/XCEditor/Widgets/UIEditorFieldRowLayout.h index 29032178..aa74cdf3 100644 --- a/new_editor/include/XCEditor/Widgets/UIEditorFieldRowLayout.h +++ b/new_editor/include/XCEditor/Widgets/UIEditorFieldRowLayout.h @@ -27,6 +27,10 @@ struct UIEditorInspectorFieldStyleTokens { ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); ::XCEngine::UI::UIColor controlFocusedBorderColor = ::XCEngine::UI::UIColor(0.19f, 0.19f, 0.19f, 1.0f); + ::XCEngine::UI::UIColor prefixColor = + ::XCEngine::UI::UIColor(0.13f, 0.13f, 0.13f, 1.0f); + ::XCEngine::UI::UIColor prefixBorderColor = + ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); ::XCEngine::UI::UIColor axisXColor = ::XCEngine::UI::UIColor(0.78f, 0.42f, 0.42f, 1.0f); ::XCEngine::UI::UIColor axisYColor = @@ -94,7 +98,6 @@ struct UIEditorFieldRowLayoutMetrics { float horizontalPadding = 12.0f; float labelControlGap = 20.0f; float controlColumnStart = 236.0f; - float sharedControlColumnMinWidth = 0.0f; float controlTrailingInset = 8.0f; float controlInsetY = 1.0f; }; diff --git a/new_editor/src/Fields/UIEditorAssetField.cpp b/new_editor/src/Fields/UIEditorAssetField.cpp index 2c72cafd..ae817718 100644 --- a/new_editor/src/Fields/UIEditorAssetField.cpp +++ b/new_editor/src/Fields/UIEditorAssetField.cpp @@ -130,7 +130,6 @@ UIEditorAssetFieldLayout BuildUIEditorAssetFieldLayout( metrics.horizontalPadding, metrics.labelControlGap, metrics.controlColumnStart, - metrics.sharedControlColumnMinWidth, metrics.controlTrailingInset, metrics.controlInsetY, }); diff --git a/new_editor/src/Fields/UIEditorBoolField.cpp b/new_editor/src/Fields/UIEditorBoolField.cpp index f9870a08..3bb14d0b 100644 --- a/new_editor/src/Fields/UIEditorBoolField.cpp +++ b/new_editor/src/Fields/UIEditorBoolField.cpp @@ -46,7 +46,6 @@ UIEditorBoolFieldLayout BuildUIEditorBoolFieldLayout( metrics.horizontalPadding, metrics.labelControlGap, metrics.controlColumnStart, - metrics.sharedControlColumnMinWidth, metrics.controlTrailingInset, 0.0f, }); diff --git a/new_editor/src/Fields/UIEditorColorField.cpp b/new_editor/src/Fields/UIEditorColorField.cpp index 5e3e65ce..e53b26d9 100644 --- a/new_editor/src/Fields/UIEditorColorField.cpp +++ b/new_editor/src/Fields/UIEditorColorField.cpp @@ -120,7 +120,6 @@ UIEditorColorFieldLayout BuildUIEditorColorFieldLayout( resolvedMetrics.horizontalPadding, resolvedMetrics.labelControlGap, resolvedMetrics.controlColumnStart, - resolvedMetrics.sharedControlColumnMinWidth, resolvedMetrics.controlTrailingInset, resolvedMetrics.swatchInsetY, }); diff --git a/new_editor/src/Fields/UIEditorEnumField.cpp b/new_editor/src/Fields/UIEditorEnumField.cpp index 546abd2a..f63317c8 100644 --- a/new_editor/src/Fields/UIEditorEnumField.cpp +++ b/new_editor/src/Fields/UIEditorEnumField.cpp @@ -115,7 +115,6 @@ UIEditorEnumFieldLayout BuildUIEditorEnumFieldLayout( resolvedMetrics.horizontalPadding, resolvedMetrics.labelControlGap, resolvedMetrics.controlColumnStart, - resolvedMetrics.sharedControlColumnMinWidth, resolvedMetrics.controlTrailingInset, resolvedMetrics.controlInsetY, }); diff --git a/new_editor/src/Fields/UIEditorFieldStyle.cpp b/new_editor/src/Fields/UIEditorFieldStyle.cpp index a1daa105..64fb8f95 100644 --- a/new_editor/src/Fields/UIEditorFieldStyle.cpp +++ b/new_editor/src/Fields/UIEditorFieldStyle.cpp @@ -17,18 +17,17 @@ const Widgets::UIEditorPropertyGridMetrics& GetUIEditorFixedPropertyGridMetrics( ResolveUIEditorTreeViewMetrics(); Widgets::UIEditorPropertyGridMetrics metrics = {}; metrics.contentInset = 0.0f; - metrics.sectionGap = 0.0f; + metrics.sectionGap = 4.0f; metrics.sectionHeaderHeight = 24.0f; metrics.fieldRowHeight = 22.0f; metrics.rowGap = 3.0f; metrics.horizontalPadding = 12.0f; metrics.sectionHeaderHorizontalPadding = 6.0f; metrics.controlColumnStart = 236.0f; - metrics.sharedControlColumnMinWidth = 160.0f; metrics.labelControlGap = 20.0f; metrics.disclosureExtent = treeMetrics.disclosureExtent; metrics.disclosureLabelGap = treeMetrics.disclosureLabelGap; - metrics.sectionTextInsetY = 0.0f; + metrics.sectionTextInsetY = 6.0f; metrics.sectionFontSize = 12.0f; metrics.labelTextInsetY = 0.0f; metrics.labelFontSize = 11.0f; @@ -83,7 +82,6 @@ Widgets::UIEditorBoolFieldMetrics BuildUIEditorPropertyGridBoolFieldMetrics( hosted.horizontalPadding = propertyGridMetrics.horizontalPadding; hosted.labelControlGap = propertyGridMetrics.labelControlGap; hosted.controlColumnStart = propertyGridMetrics.controlColumnStart; - hosted.sharedControlColumnMinWidth = propertyGridMetrics.sharedControlColumnMinWidth; hosted.controlTrailingInset = propertyGridMetrics.valueBoxInsetX; hosted.labelTextInsetY = propertyGridMetrics.labelTextInsetY; hosted.labelFontSize = propertyGridMetrics.labelFontSize; @@ -121,7 +119,6 @@ Widgets::UIEditorNumberFieldMetrics BuildUIEditorPropertyGridNumberFieldMetrics( hosted.horizontalPadding = propertyGridMetrics.horizontalPadding; hosted.labelControlGap = propertyGridMetrics.labelControlGap; hosted.controlColumnStart = propertyGridMetrics.controlColumnStart; - hosted.sharedControlColumnMinWidth = propertyGridMetrics.sharedControlColumnMinWidth; hosted.controlTrailingInset = propertyGridMetrics.valueBoxInsetX; hosted.controlInsetY = propertyGridMetrics.valueBoxInsetY; hosted.labelTextInsetY = propertyGridMetrics.labelTextInsetY; @@ -165,7 +162,6 @@ Widgets::UIEditorTextFieldMetrics BuildUIEditorPropertyGridTextFieldMetrics( hosted.horizontalPadding = propertyGridMetrics.horizontalPadding; hosted.labelControlGap = propertyGridMetrics.labelControlGap; hosted.controlColumnStart = propertyGridMetrics.controlColumnStart; - hosted.sharedControlColumnMinWidth = propertyGridMetrics.sharedControlColumnMinWidth; hosted.controlTrailingInset = propertyGridMetrics.valueBoxInsetX; hosted.controlInsetY = propertyGridMetrics.valueBoxInsetY; hosted.labelTextInsetY = propertyGridMetrics.labelTextInsetY; @@ -209,7 +205,6 @@ Widgets::UIEditorVector2FieldMetrics BuildUIEditorPropertyGridVector2FieldMetric hosted.horizontalPadding = propertyGridMetrics.horizontalPadding; hosted.labelControlGap = propertyGridMetrics.labelControlGap; hosted.controlColumnStart = propertyGridMetrics.controlColumnStart; - hosted.sharedControlColumnMinWidth = propertyGridMetrics.sharedControlColumnMinWidth; hosted.controlTrailingInset = propertyGridMetrics.valueBoxInsetX; hosted.controlInsetY = propertyGridMetrics.valueBoxInsetY; hosted.labelTextInsetY = propertyGridMetrics.labelTextInsetY; @@ -239,6 +234,8 @@ Widgets::UIEditorVector2FieldPalette BuildUIEditorPropertyGridVector2FieldPalett hosted.readOnlyColor = propertyGridPalette.valueBoxReadOnlyColor; hosted.componentBorderColor = propertyGridPalette.valueBoxBorderColor; hosted.componentFocusedBorderColor = propertyGridPalette.valueBoxEditingBorderColor; + hosted.prefixColor = propertyGridPalette.valueBoxHoverColor; + hosted.prefixBorderColor = propertyGridPalette.valueBoxBorderColor; hosted.labelColor = propertyGridPalette.labelTextColor; hosted.valueColor = propertyGridPalette.valueTextColor; hosted.readOnlyValueColor = propertyGridPalette.readOnlyValueTextColor; @@ -253,7 +250,6 @@ Widgets::UIEditorVector3FieldMetrics BuildUIEditorPropertyGridVector3FieldMetric hosted.horizontalPadding = propertyGridMetrics.horizontalPadding; hosted.labelControlGap = propertyGridMetrics.labelControlGap; hosted.controlColumnStart = propertyGridMetrics.controlColumnStart; - hosted.sharedControlColumnMinWidth = propertyGridMetrics.sharedControlColumnMinWidth; hosted.controlTrailingInset = propertyGridMetrics.valueBoxInsetX; hosted.controlInsetY = propertyGridMetrics.valueBoxInsetY; hosted.labelTextInsetY = propertyGridMetrics.labelTextInsetY; @@ -283,6 +279,8 @@ Widgets::UIEditorVector3FieldPalette BuildUIEditorPropertyGridVector3FieldPalett hosted.readOnlyColor = propertyGridPalette.valueBoxReadOnlyColor; hosted.componentBorderColor = propertyGridPalette.valueBoxBorderColor; hosted.componentFocusedBorderColor = propertyGridPalette.valueBoxEditingBorderColor; + hosted.prefixColor = propertyGridPalette.valueBoxHoverColor; + hosted.prefixBorderColor = propertyGridPalette.valueBoxBorderColor; hosted.labelColor = propertyGridPalette.labelTextColor; hosted.valueColor = propertyGridPalette.valueTextColor; hosted.readOnlyValueColor = propertyGridPalette.readOnlyValueTextColor; @@ -297,7 +295,6 @@ Widgets::UIEditorVector4FieldMetrics BuildUIEditorPropertyGridVector4FieldMetric hosted.horizontalPadding = propertyGridMetrics.horizontalPadding; hosted.labelControlGap = propertyGridMetrics.labelControlGap; hosted.controlColumnStart = propertyGridMetrics.controlColumnStart; - hosted.sharedControlColumnMinWidth = propertyGridMetrics.sharedControlColumnMinWidth; hosted.controlTrailingInset = propertyGridMetrics.valueBoxInsetX; hosted.controlInsetY = propertyGridMetrics.valueBoxInsetY; hosted.labelTextInsetY = propertyGridMetrics.labelTextInsetY; @@ -327,6 +324,8 @@ Widgets::UIEditorVector4FieldPalette BuildUIEditorPropertyGridVector4FieldPalett hosted.readOnlyColor = propertyGridPalette.valueBoxReadOnlyColor; hosted.componentBorderColor = propertyGridPalette.valueBoxBorderColor; hosted.componentFocusedBorderColor = propertyGridPalette.valueBoxEditingBorderColor; + hosted.prefixColor = propertyGridPalette.valueBoxHoverColor; + hosted.prefixBorderColor = propertyGridPalette.valueBoxBorderColor; hosted.labelColor = propertyGridPalette.labelTextColor; hosted.valueColor = propertyGridPalette.valueTextColor; hosted.readOnlyValueColor = propertyGridPalette.readOnlyValueTextColor; @@ -341,7 +340,6 @@ Widgets::UIEditorEnumFieldMetrics BuildUIEditorPropertyGridEnumFieldMetrics( hosted.horizontalPadding = propertyGridMetrics.horizontalPadding; hosted.labelControlGap = propertyGridMetrics.labelControlGap; hosted.controlColumnStart = propertyGridMetrics.controlColumnStart; - hosted.sharedControlColumnMinWidth = propertyGridMetrics.sharedControlColumnMinWidth; hosted.controlTrailingInset = propertyGridMetrics.valueBoxInsetX; hosted.controlInsetY = propertyGridMetrics.valueBoxInsetY; hosted.labelTextInsetY = propertyGridMetrics.labelTextInsetY; @@ -386,7 +384,6 @@ Widgets::UIEditorColorFieldMetrics BuildUIEditorPropertyGridColorFieldMetrics( hosted.horizontalPadding = propertyGridMetrics.horizontalPadding; hosted.labelControlGap = propertyGridMetrics.labelControlGap; hosted.controlColumnStart = propertyGridMetrics.controlColumnStart; - hosted.sharedControlColumnMinWidth = propertyGridMetrics.sharedControlColumnMinWidth; hosted.controlTrailingInset = propertyGridMetrics.valueBoxInsetX; hosted.swatchWidth = boolMetrics.checkboxSize; hosted.swatchInsetY = @@ -430,7 +427,6 @@ Widgets::UIEditorObjectFieldMetrics BuildUIEditorPropertyGridObjectFieldMetrics( hosted.horizontalPadding = propertyGridMetrics.horizontalPadding; hosted.labelControlGap = propertyGridMetrics.labelControlGap; hosted.controlColumnStart = propertyGridMetrics.controlColumnStart; - hosted.sharedControlColumnMinWidth = propertyGridMetrics.sharedControlColumnMinWidth; hosted.controlTrailingInset = propertyGridMetrics.valueBoxInsetX; hosted.controlInsetY = propertyGridMetrics.valueBoxInsetY; hosted.labelTextInsetY = propertyGridMetrics.labelTextInsetY; @@ -483,7 +479,6 @@ Widgets::UIEditorAssetFieldMetrics BuildUIEditorPropertyGridAssetFieldMetrics( hosted.horizontalPadding = propertyGridMetrics.horizontalPadding; hosted.labelControlGap = propertyGridMetrics.labelControlGap; hosted.controlColumnStart = propertyGridMetrics.controlColumnStart; - hosted.sharedControlColumnMinWidth = propertyGridMetrics.sharedControlColumnMinWidth; hosted.controlTrailingInset = propertyGridMetrics.valueBoxInsetX; hosted.controlInsetY = propertyGridMetrics.valueBoxInsetY; hosted.labelTextInsetY = propertyGridMetrics.labelTextInsetY; diff --git a/new_editor/src/Fields/UIEditorNumberField.cpp b/new_editor/src/Fields/UIEditorNumberField.cpp index 6f06bc79..7485c7cc 100644 --- a/new_editor/src/Fields/UIEditorNumberField.cpp +++ b/new_editor/src/Fields/UIEditorNumberField.cpp @@ -233,7 +233,6 @@ UIEditorNumberFieldLayout BuildUIEditorNumberFieldLayout( resolvedMetrics.horizontalPadding, resolvedMetrics.labelControlGap, resolvedMetrics.controlColumnStart, - resolvedMetrics.sharedControlColumnMinWidth, resolvedMetrics.controlTrailingInset, resolvedMetrics.controlInsetY, }); diff --git a/new_editor/src/Fields/UIEditorObjectField.cpp b/new_editor/src/Fields/UIEditorObjectField.cpp index 1e368f8b..8fd509d7 100644 --- a/new_editor/src/Fields/UIEditorObjectField.cpp +++ b/new_editor/src/Fields/UIEditorObjectField.cpp @@ -119,7 +119,6 @@ UIEditorObjectFieldLayout BuildUIEditorObjectFieldLayout( metrics.horizontalPadding, metrics.labelControlGap, metrics.controlColumnStart, - metrics.sharedControlColumnMinWidth, metrics.controlTrailingInset, metrics.controlInsetY, }); diff --git a/new_editor/src/Fields/UIEditorPropertyGrid.cpp b/new_editor/src/Fields/UIEditorPropertyGrid.cpp index 8b80a179..ce49881c 100644 --- a/new_editor/src/Fields/UIEditorPropertyGrid.cpp +++ b/new_editor/src/Fields/UIEditorPropertyGrid.cpp @@ -864,18 +864,11 @@ void AppendUIEditorPropertyGridForeground( layout.sectionDisclosureRects[sectionVisibleIndex], layout.sectionExpanded[sectionVisibleIndex], palette.disclosureColor); - drawList.PushClipRect( - ResolveUIEditorTextClipRect( - layout.sectionTitleRects[sectionVisibleIndex], - metrics.sectionFontSize), - true); + drawList.PushClipRect(layout.sectionTitleRects[sectionVisibleIndex], true); drawList.AddText( ::XCEngine::UI::UIPoint( layout.sectionTitleRects[sectionVisibleIndex].x, - ResolveUIEditorTextTop( - layout.sectionTitleRects[sectionVisibleIndex], - metrics.sectionFontSize, - metrics.sectionTextInsetY)), + layout.sectionTitleRects[sectionVisibleIndex].y + metrics.sectionTextInsetY), section.title, palette.sectionTextColor, metrics.sectionFontSize); diff --git a/new_editor/src/Fields/UIEditorPropertyGridInteraction.cpp b/new_editor/src/Fields/UIEditorPropertyGridInteraction.cpp index 79dee0b3..f5b5a468 100644 --- a/new_editor/src/Fields/UIEditorPropertyGridInteraction.cpp +++ b/new_editor/src/Fields/UIEditorPropertyGridInteraction.cpp @@ -1201,7 +1201,6 @@ void PruneStateEntries( template bool HasMeaningfulResult( const typename Traits::InteractionResult& result, - const typename Traits::FieldStateType& previousFieldState, const typename Traits::InteractionState& interactionState, bool hadFocus, bool hadEditing) { @@ -1212,9 +1211,6 @@ bool HasMeaningfulResult( result.editCommitted || Traits::HasEditCommitRejected(result) || result.editCanceled || - Traits::HasHoverStateChanged( - previousFieldState, - Traits::FieldState(interactionState)) || result.hitTarget.kind != Traits::kNoneHitTargetKind || hadFocus || hadEditing || @@ -1301,8 +1297,6 @@ bool ProcessFieldEventImpl( BuildInteractionState(state, field.fieldId); const bool hadFocus = Traits::FieldState(interactionState).focused; const bool hadEditing = Traits::FieldState(interactionState).editing; - const typename Traits::FieldStateType previousFieldState = - Traits::FieldState(interactionState); typename Traits::Spec spec = Traits::BuildSpec(field); const typename Traits::InteractionFrame frame = Traits::Update( @@ -1314,7 +1308,6 @@ bool ProcessFieldEventImpl( if (!HasMeaningfulResult( frame.result, - previousFieldState, interactionState, hadFocus, hadEditing)) { @@ -1422,7 +1415,6 @@ struct NumberTraits { using Metrics = Widgets::UIEditorNumberFieldMetrics; using Spec = Widgets::UIEditorNumberFieldSpec; using HitTargetKind = Widgets::UIEditorNumberFieldHitTargetKind; - using FieldStateType = Widgets::UIEditorNumberFieldState; static constexpr UIEditorPropertyGridFieldKind kFieldKind = UIEditorPropertyGridFieldKind::Number; @@ -1478,12 +1470,6 @@ struct NumberTraits { static bool HasEditCommitRejected(const InteractionResult& result) { return result.editCommitRejected; } - - static bool HasHoverStateChanged( - const FieldStateType& before, - const FieldStateType& after) { - return before.hoveredTarget != after.hoveredTarget; - } }; struct TextTraits { @@ -1494,7 +1480,6 @@ struct TextTraits { using Metrics = Widgets::UIEditorTextFieldMetrics; using Spec = Widgets::UIEditorTextFieldSpec; using HitTargetKind = Widgets::UIEditorTextFieldHitTargetKind; - using FieldStateType = Widgets::UIEditorTextFieldState; static constexpr UIEditorPropertyGridFieldKind kFieldKind = UIEditorPropertyGridFieldKind::Text; @@ -1550,12 +1535,6 @@ struct TextTraits { static bool HasEditCommitRejected(const InteractionResult&) { return false; } - - static bool HasHoverStateChanged( - const FieldStateType& before, - const FieldStateType& after) { - return before.hoveredTarget != after.hoveredTarget; - } }; template <> @@ -2482,7 +2461,6 @@ void PruneStateEntries( template bool HasMeaningfulResult( const typename Traits::InteractionResult& result, - const typename Traits::FieldStateType& previousFieldState, const typename Traits::InteractionState& interactionState, bool hadFocus, bool hadEditing) { @@ -2495,9 +2473,6 @@ bool HasMeaningfulResult( result.editCommitted || result.editCommitRejected || result.editCanceled || - Traits::HasHoverStateChanged( - previousFieldState, - Traits::FieldState(interactionState)) || result.hitTarget.kind != Traits::kNoneHitTargetKind || hadFocus || hadEditing || @@ -2558,8 +2533,6 @@ bool ProcessVectorFieldEventImpl( BuildInteractionState(state, field.fieldId); const bool hadFocus = Traits::FieldState(vectorState).focused; const bool hadEditing = Traits::FieldState(vectorState).editing; - const typename Traits::FieldStateType previousFieldState = - Traits::FieldState(vectorState); typename Traits::Spec spec = Traits::BuildSpec(field); const typename Traits::InteractionFrame frame = Traits::Update( @@ -2571,7 +2544,6 @@ bool ProcessVectorFieldEventImpl( if (!HasMeaningfulResult( frame.result, - previousFieldState, vectorState, hadFocus, hadEditing)) { @@ -2679,7 +2651,6 @@ struct Vector2Traits { using InteractionFrame = UIEditorVector2FieldInteractionFrame; using Metrics = Widgets::UIEditorVector2FieldMetrics; using Spec = Widgets::UIEditorVector2FieldSpec; - using FieldStateType = Widgets::UIEditorVector2FieldState; static constexpr UIEditorPropertyGridFieldKind kFieldKind = UIEditorPropertyGridFieldKind::Vector2; @@ -2727,13 +2698,6 @@ struct Vector2Traits { inputEvents, metrics); } - - static bool HasHoverStateChanged( - const FieldStateType& before, - const FieldStateType& after) { - return before.hoveredTarget != after.hoveredTarget || - before.hoveredComponentIndex != after.hoveredComponentIndex; - } }; struct Vector3Traits { @@ -2743,7 +2707,6 @@ struct Vector3Traits { using InteractionFrame = UIEditorVector3FieldInteractionFrame; using Metrics = Widgets::UIEditorVector3FieldMetrics; using Spec = Widgets::UIEditorVector3FieldSpec; - using FieldStateType = Widgets::UIEditorVector3FieldState; static constexpr UIEditorPropertyGridFieldKind kFieldKind = UIEditorPropertyGridFieldKind::Vector3; @@ -2791,13 +2754,6 @@ struct Vector3Traits { inputEvents, metrics); } - - static bool HasHoverStateChanged( - const FieldStateType& before, - const FieldStateType& after) { - return before.hoveredTarget != after.hoveredTarget || - before.hoveredComponentIndex != after.hoveredComponentIndex; - } }; struct Vector4Traits { @@ -2807,7 +2763,6 @@ struct Vector4Traits { using InteractionFrame = UIEditorVector4FieldInteractionFrame; using Metrics = Widgets::UIEditorVector4FieldMetrics; using Spec = Widgets::UIEditorVector4FieldSpec; - using FieldStateType = Widgets::UIEditorVector4FieldState; static constexpr UIEditorPropertyGridFieldKind kFieldKind = UIEditorPropertyGridFieldKind::Vector4; @@ -2855,13 +2810,6 @@ struct Vector4Traits { inputEvents, metrics); } - - static bool HasHoverStateChanged( - const FieldStateType& before, - const FieldStateType& after) { - return before.hoveredTarget != after.hoveredTarget || - before.hoveredComponentIndex != after.hoveredComponentIndex; - } }; template <> diff --git a/new_editor/src/Fields/UIEditorTextField.cpp b/new_editor/src/Fields/UIEditorTextField.cpp index 8dbeac48..253fde4c 100644 --- a/new_editor/src/Fields/UIEditorTextField.cpp +++ b/new_editor/src/Fields/UIEditorTextField.cpp @@ -117,7 +117,6 @@ UIEditorTextFieldLayout BuildUIEditorTextFieldLayout( resolvedMetrics.horizontalPadding, resolvedMetrics.labelControlGap, resolvedMetrics.controlColumnStart, - resolvedMetrics.sharedControlColumnMinWidth, resolvedMetrics.controlTrailingInset, resolvedMetrics.controlInsetY, }); diff --git a/new_editor/src/Fields/UIEditorVector2Field.cpp b/new_editor/src/Fields/UIEditorVector2Field.cpp index 1971f696..68fa8204 100644 --- a/new_editor/src/Fields/UIEditorVector2Field.cpp +++ b/new_editor/src/Fields/UIEditorVector2Field.cpp @@ -68,6 +68,14 @@ UIEditorVector2FieldPalette ResolvePalette(const UIEditorVector2FieldPalette& pa ::XCEngine::UI::UIColor(0.20f, 0.20f, 0.20f, 1.0f))) { resolved.componentFocusedBorderColor = tokens.controlFocusedBorderColor; } + if (AreUIEditorFieldColorsEqual(palette.prefixColor, ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f))) { + resolved.prefixColor = tokens.prefixColor; + } + if (AreUIEditorFieldColorsEqual( + palette.prefixBorderColor, + ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f))) { + resolved.prefixBorderColor = tokens.prefixBorderColor; + } if (AreUIEditorFieldColorsEqual(palette.labelColor, ::XCEngine::UI::UIColor(0.80f, 0.80f, 0.80f, 1.0f))) { resolved.labelColor = tokens.labelColor; } @@ -192,7 +200,6 @@ UIEditorVector2FieldLayout BuildUIEditorVector2FieldLayout( resolvedMetrics.horizontalPadding, resolvedMetrics.labelControlGap, resolvedMetrics.controlColumnStart, - resolvedMetrics.sharedControlColumnMinWidth, resolvedMetrics.controlTrailingInset, resolvedMetrics.controlInsetY, }); @@ -265,11 +272,11 @@ void AppendUIEditorVector2FieldBackground( for (std::size_t componentIndex = 0u; componentIndex < layout.componentRects.size(); ++componentIndex) { drawList.AddFilledRect( - layout.componentRects[componentIndex], + layout.componentValueRects[componentIndex], ResolveComponentFillColor(spec, state, resolvedPalette, componentIndex), resolvedMetrics.componentRounding); drawList.AddRectOutline( - layout.componentRects[componentIndex], + layout.componentValueRects[componentIndex], ResolveComponentBorderColor(state, resolvedPalette, componentIndex), resolvedMetrics.borderThickness, resolvedMetrics.componentRounding); diff --git a/new_editor/src/Fields/UIEditorVector3Field.cpp b/new_editor/src/Fields/UIEditorVector3Field.cpp index 7044ea77..a804cd5c 100644 --- a/new_editor/src/Fields/UIEditorVector3Field.cpp +++ b/new_editor/src/Fields/UIEditorVector3Field.cpp @@ -68,6 +68,14 @@ UIEditorVector3FieldPalette ResolvePalette(const UIEditorVector3FieldPalette& pa ::XCEngine::UI::UIColor(0.20f, 0.20f, 0.20f, 1.0f))) { resolved.componentFocusedBorderColor = tokens.controlFocusedBorderColor; } + if (AreUIEditorFieldColorsEqual(palette.prefixColor, ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f))) { + resolved.prefixColor = tokens.prefixColor; + } + if (AreUIEditorFieldColorsEqual( + palette.prefixBorderColor, + ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f))) { + resolved.prefixBorderColor = tokens.prefixBorderColor; + } if (AreUIEditorFieldColorsEqual(palette.labelColor, ::XCEngine::UI::UIColor(0.80f, 0.80f, 0.80f, 1.0f))) { resolved.labelColor = tokens.labelColor; } @@ -195,7 +203,6 @@ UIEditorVector3FieldLayout BuildUIEditorVector3FieldLayout( resolvedMetrics.horizontalPadding, resolvedMetrics.labelControlGap, resolvedMetrics.controlColumnStart, - resolvedMetrics.sharedControlColumnMinWidth, resolvedMetrics.controlTrailingInset, resolvedMetrics.controlInsetY, }); @@ -268,11 +275,11 @@ void AppendUIEditorVector3FieldBackground( for (std::size_t componentIndex = 0u; componentIndex < layout.componentRects.size(); ++componentIndex) { drawList.AddFilledRect( - layout.componentRects[componentIndex], + layout.componentValueRects[componentIndex], ResolveComponentFillColor(spec, state, resolvedPalette, componentIndex), resolvedMetrics.componentRounding); drawList.AddRectOutline( - layout.componentRects[componentIndex], + layout.componentValueRects[componentIndex], ResolveComponentBorderColor(state, resolvedPalette, componentIndex), resolvedMetrics.borderThickness, resolvedMetrics.componentRounding); diff --git a/new_editor/src/Fields/UIEditorVector4Field.cpp b/new_editor/src/Fields/UIEditorVector4Field.cpp index 54bd290e..21e0731d 100644 --- a/new_editor/src/Fields/UIEditorVector4Field.cpp +++ b/new_editor/src/Fields/UIEditorVector4Field.cpp @@ -68,6 +68,14 @@ UIEditorVector4FieldPalette ResolvePalette(const UIEditorVector4FieldPalette& pa ::XCEngine::UI::UIColor(0.20f, 0.20f, 0.20f, 1.0f))) { resolved.componentFocusedBorderColor = tokens.controlFocusedBorderColor; } + if (AreUIEditorFieldColorsEqual(palette.prefixColor, ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f))) { + resolved.prefixColor = tokens.prefixColor; + } + if (AreUIEditorFieldColorsEqual( + palette.prefixBorderColor, + ::XCEngine::UI::UIColor(0.0f, 0.0f, 0.0f, 0.0f))) { + resolved.prefixBorderColor = tokens.prefixBorderColor; + } if (AreUIEditorFieldColorsEqual(palette.labelColor, ::XCEngine::UI::UIColor(0.80f, 0.80f, 0.80f, 1.0f))) { resolved.labelColor = tokens.labelColor; } @@ -198,7 +206,6 @@ UIEditorVector4FieldLayout BuildUIEditorVector4FieldLayout( resolvedMetrics.horizontalPadding, resolvedMetrics.labelControlGap, resolvedMetrics.controlColumnStart, - resolvedMetrics.sharedControlColumnMinWidth, resolvedMetrics.controlTrailingInset, resolvedMetrics.controlInsetY, }); @@ -271,11 +278,11 @@ void AppendUIEditorVector4FieldBackground( for (std::size_t componentIndex = 0u; componentIndex < layout.componentRects.size(); ++componentIndex) { drawList.AddFilledRect( - layout.componentRects[componentIndex], + layout.componentValueRects[componentIndex], ResolveComponentFillColor(spec, state, resolvedPalette, componentIndex), resolvedMetrics.componentRounding); drawList.AddRectOutline( - layout.componentRects[componentIndex], + layout.componentValueRects[componentIndex], ResolveComponentBorderColor(state, resolvedPalette, componentIndex), resolvedMetrics.borderThickness, resolvedMetrics.componentRounding); diff --git a/new_editor/src/Shell/UIEditorShellCompose.cpp b/new_editor/src/Shell/UIEditorShellCompose.cpp index ccf48dcd..d75ed579 100644 --- a/new_editor/src/Shell/UIEditorShellCompose.cpp +++ b/new_editor/src/Shell/UIEditorShellCompose.cpp @@ -119,6 +119,16 @@ void AppendUIEditorShellToolbar( return; } + drawList.AddFilledRect( + layout.groupRect, + palette.groupColor, + metrics.groupCornerRounding); + drawList.AddRectOutline( + layout.groupRect, + palette.groupBorderColor, + metrics.borderThickness, + metrics.groupCornerRounding); + const std::size_t buttonCount = (std::min)(buttons.size(), layout.buttonRects.size()); for (std::size_t index = 0; index < buttonCount; ++index) { const UIRect& buttonRect = layout.buttonRects[index]; diff --git a/new_editor/src/Widgets/UIEditorFieldRowLayout.cpp b/new_editor/src/Widgets/UIEditorFieldRowLayout.cpp index f335a7a5..03fc8afc 100644 --- a/new_editor/src/Widgets/UIEditorFieldRowLayout.cpp +++ b/new_editor/src/Widgets/UIEditorFieldRowLayout.cpp @@ -31,6 +31,8 @@ const UIEditorInspectorFieldStyleTokens& GetUIEditorInspectorFieldStyleTokens() tokens.controlReadOnlyColor = ::XCEngine::UI::UIColor(0.10f, 0.10f, 0.10f, 1.0f); tokens.controlBorderColor = ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); tokens.controlFocusedBorderColor = ::XCEngine::UI::UIColor(0.19f, 0.19f, 0.19f, 1.0f); + tokens.prefixColor = ::XCEngine::UI::UIColor(0.13f, 0.13f, 0.13f, 1.0f); + tokens.prefixBorderColor = ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); tokens.popupColor = ::XCEngine::UI::UIColor(0.10f, 0.10f, 0.10f, 1.0f); tokens.popupBorderColor = ::XCEngine::UI::UIColor(0.15f, 0.15f, 0.15f, 1.0f); tokens.popupHeaderColor = ::XCEngine::UI::UIColor(0.11f, 0.11f, 0.11f, 1.0f); @@ -72,37 +74,26 @@ UIEditorFieldRowLayout BuildUIEditorFieldRowLayout( const float rowHeight = bounds.height > 0.0f ? bounds.height : metrics.rowHeight; const ::XCEngine::UI::UIRect rowBounds(bounds.x, bounds.y, bounds.width, rowHeight); - const float horizontalPadding = ClampNonNegative(metrics.horizontalPadding); - const float trailingInset = ClampNonNegative(metrics.controlTrailingInset); - const float defaultGap = ClampNonNegative(metrics.labelControlGap); - const float contentLeft = rowBounds.x + horizontalPadding; - const float contentRight = - rowBounds.x + ClampNonNegative(rowBounds.width) - trailingInset; - const float contentWidth = ClampNonNegative(contentRight - contentLeft); - const float requestedReservedControlWidth = ClampNonNegative( - metrics.sharedControlColumnMinWidth > 0.0f - ? metrics.sharedControlColumnMinWidth - : minimumControlWidth); - const float reservedControlWidth = - ClampNonNegative((std::min)(requestedReservedControlWidth, contentWidth)); - const float effectiveGap = - (std::min)(defaultGap, ClampNonNegative(contentWidth - reservedControlWidth)); - const float preferredControlX = - (std::clamp)(rowBounds.x + metrics.controlColumnStart, contentLeft, contentRight); - const float maximumControlX = contentRight - reservedControlWidth; + const float resolvedMinimumControlWidth = + ClampNonNegative((std::min)(minimumControlWidth, rowBounds.width)); + const float preferredControlX = rowBounds.x + metrics.controlColumnStart; + const float maximumControlX = + rowBounds.x + rowBounds.width - metrics.controlTrailingInset - resolvedMinimumControlWidth; const float controlX = - (std::clamp)((std::min)(preferredControlX, maximumControlX), contentLeft, contentRight); - + (std::clamp)( + (std::min)(preferredControlX, maximumControlX), + rowBounds.x, + rowBounds.x + rowBounds.width - metrics.controlTrailingInset); const float controlInsetY = (std::min)(metrics.controlInsetY, rowBounds.height * 0.25f); const float controlWidth = - ClampNonNegative(contentRight - controlX); + ClampNonNegative(rowBounds.x + rowBounds.width - metrics.controlTrailingInset - controlX); UIEditorFieldRowLayout layout = {}; layout.bounds = rowBounds; layout.labelRect = ::XCEngine::UI::UIRect( - contentLeft, + rowBounds.x + metrics.horizontalPadding, rowBounds.y, - ClampNonNegative(controlX - effectiveGap - contentLeft), + ClampNonNegative(controlX - metrics.labelControlGap - rowBounds.x - metrics.horizontalPadding), rowBounds.height); layout.controlRect = ::XCEngine::UI::UIRect( controlX,