Hide ImGui compat factories behind legacy XCUI interop

This commit is contained in:
2026-04-05 16:26:29 +08:00
parent f03a8f63ec
commit 9bad86eccf
11 changed files with 127 additions and 35 deletions

View File

@@ -32,11 +32,16 @@ Old `editor` replacement is explicitly out of scope for this phase.
- `Application.cpp` no longer directly includes `<imgui.h>`, even though the compatibility host path is still compiled into `new_editor`
- The `new_editor` build now also has an explicit compatibility-source slice:
- legacy ImGui shell sources and vendored ImGui backend sources are now grouped into a dedicated compatibility static library instead of being compiled directly as part of the main `XCNewEditor` source list
- the main `XCNewEditor` target no longer compiles those legacy sources directly, but it still carries `${IMGUI_SOURCE_DIR}` / `${IMGUI_SOURCE_DIR}/backends` on its private include surface and still reaches `<imgui.h>` through older editor bridge headers such as `editor/src/UI/ImGuiBackendBridge.h`
- The next checkpoint to append after this file should be the main-target header-isolation milestone:
- The main-target header-isolation milestone is now also landed in `new_editor`:
- `XCNewEditor` drops direct `${IMGUI_SOURCE_DIR}` / `${IMGUI_SOURCE_DIR}/backends` include-directory requirements
- the generated `XCNewEditor.vcxproj` no longer advertises `imgui-src` or `editor/src` on the default include surface
- the default native compile path no longer reaches `<imgui.h>` through `editor/src/UI/ImGuiBackendBridge.h` or equivalent bridge headers
- `XCNewEditorImGuiCompat` remains the explicit compatibility-only consumer of legacy ImGui headers and backend sources
- The compositor/font compat boundary is now tighter as well:
- `IWindowUICompositor.h` and `IEditorHostCompositor.h` no longer declare `CreateImGui*` factories on the generic XCUI seam
- ImGui factory entry points now live only in `LegacyImGuiHostInterop.h`
- `XCUIEditorFontSetup.h` no longer exposes `ImFont` / `ImFontAtlas`; the public API is now a current-context font bootstrap helper
- `XCUIStandaloneTextAtlasProvider.h` no longer depends on the ImGui-oriented font bootstrap header
- Old `editor` replacement remains deferred; all active execution still stays inside XCUI shared code and `new_editor`.
## Three-Layer Status
@@ -131,7 +136,7 @@ Current gap:
Current gap:
- The default shell host is now native, but the legacy ImGui shell and panel path still exists as a compatibility host and is still compiled into `new_editor`.
- The default native shell path is now split away from direct `ImGui::*` calls at the translation-unit/source-list level, but the main `XCNewEditor` target still keeps ImGui on its header/include surface through `target_include_directories(...)` and older editor bridge headers.
- The default native shell path is now split away from direct `ImGui::*` calls at the main-target header/include level, but the legacy ImGui shell and panel implementations still remain compiled behind `XCNewEditorImGuiCompat`.
- The native shell currently proves direct runtime composition, but its shell chrome is still a bespoke `Application`-side layout rather than a fully shared XCUI-authored editor shell document.
- Editor-specialized widgets are still incomplete at the shared-module level: the authored prototypes exist, but virtualization, multi-selection/focus traversal, toolbar/menu chrome, menu interaction widgets, and icon-atlas widgets are not yet extracted into reusable XCUI modules.
- The default native text path now uses a standalone Windows/GDI atlas through `XCUIStandaloneTextAtlasProvider`, but that provider still lives inside `new_editor` and is not yet promoted into a shared/cross-platform text subsystem.
@@ -147,6 +152,7 @@ Current gap:
- `new_editor_xcui_rhi_render_backend_tests`: `5/5`
- `new_editor_xcui_standalone_text_atlas_provider_tests`: `6/6`
- `new_editor_xcui_hosted_preview_presenter_tests`: `20/20`
- `new_editor_legacy_imgui_host_interop_tests`: `4/4`
- `new_editor_imgui_window_ui_compositor_tests`: `7/7`
- `new_editor_native_window_ui_compositor_tests`: `8/8`
- `new_editor_xcui_editor_command_router_tests`: `5/5`
@@ -237,6 +243,7 @@ Current gap:
- `ImGuiHostCompositor`
- `Application` frame/present flow routed through the compositor instead of direct `m_imguiBackend` ownership
- The window-level XCUI compositor seam now also has a dedicated regression target around `ImGuiWindowUICompositor`, covering initialization, render-frame ordering, Win32 message forwarding, texture registration forwarding, and shutdown safety.
- The legacy compatibility seam now also has dedicated regression coverage around `LegacyImGuiHostInterop`, covering compat factory wiring, ImGui input-capture forwarding, legacy font bootstrap, and the disabled demo-window path.
- The window compositor and hosted-preview seams gained more edge-case coverage around no-UI render passes, compositor re-initialization/rebinding, partial logical-size fallback, and descriptor reuse for repeated queued-frame keys.
- `new_editor` now has a dedicated `XCUIEditorCommandRouter` test target covering command registration, replacement, enable predicates, accelerator matching, and policy gates around focus/keyboard capture/text input.
- `Application` now integrates `XCUIEditorCommandRouter` into the shell itself:
@@ -292,14 +299,18 @@ Current gap:
- `new_editor` build composition is now split into main/native and compatibility slices:
- the main `XCNewEditor` target no longer compiles legacy ImGui shell/panel/backend source files directly
- legacy ImGui shell/panel/backend sources plus vendored ImGui sources now build behind a dedicated compatibility static library that the main executable links
- the remaining build-side cleanup is narrower now: `XCNewEditor` still carries ImGui include directories and older editor bridge headers on its direct compile surface, so the next checkpoint is header/include isolation rather than another source-list split
- the main target no longer carries ImGui include directories or older editor bridge headers on its direct compile surface; those remain confined to the compatibility slice
- The default XCUI compile seam is now also cleaner:
- generic compositor headers no longer advertise compat-only `CreateImGui*` factories
- compat-only host/window compositor factories now live behind `LegacyImGuiHostInterop.*`
- public font bootstrap headers no longer leak `ImFont` / `ImFontAtlas` types into generic XCUI consumers
## Phase Risks Still Open
- Schema instance validation is still open beyond `.xcschema` self-definition and artifact round-trip coverage.
- `ScrollView` is still authored/static; no wheel-driven scrolling or virtualization yet.
- `Application.cpp` is now split away from direct `ImGui::*` calls, but `XCNewEditor` still depends on ImGui at the header/include level because `new_editor/CMakeLists.txt` still adds `${IMGUI_SOURCE_DIR}` / `${IMGUI_SOURCE_DIR}/backends` to the main target and the editor bridge chain still reaches `editor/src/UI/ImGuiBackendBridge.h` -> `<imgui.h>`.
- `XCNewEditorImGuiCompat` is now the compatibility-source slice, but the main executable still links it unconditionally and has not yet reduced its direct include surface to a compat-free default-path baseline.
- `XCNewEditor` no longer depends on ImGui at the default-path header/include level, but `XCNewEditorImGuiCompat` is still linked into the executable and still houses the legacy shell/panel path.
- Legacy panel implementations such as `XCUIDemoPanel` / `XCUILayoutLabPanel` still render as ImGui windows inside the compatibility slice, so editor-layer behavior is not yet fully carried by XCUI-native shell composition.
- The default native text path now owns its atlas without ImGui, but the provider is still Windows-only and remains trapped inside `new_editor` instead of a shared/cross-platform text layer.
- Hosted-preview compatibility presentation still depends on an ImGui-only inline presenter path when not using the queued native surface path.
- Editor widget coverage is still prototype-driven inside `LayoutLab`; it has not yet been promoted into a full reusable shared widget/runtime layer with command routing, virtualization, and property-edit transactions.
@@ -307,12 +318,12 @@ Current gap:
## Execution-Plan Alignment
- Against `XCUI完整架构设计与执行计划.md`, current `new_editor` progress should be treated as an early `Phase 8` foothold rather than full `Milestone E` completion:
- landed: `NativeWindowUICompositor`, native shell packet composition, native hosted-preview publication, XCUI-owned texture registrations, native panel surface-image presentation, standalone native text-atlas ownership inside `new_editor`, legacy `Application` TU split, and `XCNewEditorImGuiCompat`
- not yet landed: main-target header/include isolation from ImGui, promotion of the native text-atlas path into a shared/cross-platform text subsystem, shared XCUI-authored editor shell chrome
- landed: `NativeWindowUICompositor`, native shell packet composition, native hosted-preview publication, XCUI-owned texture registrations, native panel surface-image presentation, standalone native text-atlas ownership inside `new_editor`, legacy `Application` TU split, `XCNewEditorImGuiCompat`, main-target header/include isolation from ImGui, and compat-only factory/font seam tightening
- not yet landed: promotion of the native text-atlas path into a shared/cross-platform text subsystem, shared XCUI-authored editor shell chrome, and retirement of legacy ImGui-window panel implementations
- That means the next de-ImGui push should not keep centering on hosted-preview publication; that milestone is now effectively closed for the default native shell path.
- The real remaining default-path blockers are:
- remove the main `XCNewEditor` target's direct ImGui header dependency by cutting the `D3D12WindowRenderer` / `ImGuiBackendBridge` include chain off the default compile path
- stop treating ImGui include paths as part of the default `new_editor` compilation surface, leaving them only on the compatibility slice
- move editor-layer behavior off the legacy ImGui-window panel implementations and into native/XCUI shell composition
- keep compat-only factories/types from drifting back into generic XCUI seams while the compatibility slice remains linked
- harden and promote `XCUIStandaloneTextAtlasProvider` / editor font bootstrap into a shared native text subsystem
- move native shell chrome out of bespoke `Application` layout code and into a shared XCUI shell model or authored shell document
@@ -320,7 +331,7 @@ Current gap:
1. Expand runtime/game-layer ownership from the current `SceneRuntime` UI context into scene-declared HUD/menu bootstrapping, draw submission, and higher-level runtime UI policies.
2. Promote the current editor-facing widget prototypes out of authored `LayoutLab` content and into reusable XCUI widget/runtime modules, then continue with toolbar/menu chrome, shell-state adoption, virtualization, and broader focus/multi-selection behavior.
3. Finish the remaining main-target de-ImGui cleanup first: remove direct ImGui header/include requirements from `XCNewEditor`, keep ImGui confined to `XCNewEditorImGuiCompat`, and append the next checkpoint once the default native compile path no longer reaches `<imgui.h>`.
4. Promote the current standalone native text/font path out of `new_editor`, harden its atlas invalidation/caching contract, and remove the remaining default-path ImGui text/bootstrap ownership around compatibility-only code.
5. Promote the native shell chrome and card layout out of bespoke `Application` code into a shared XCUI/editor-layer shell model or authored shell document.
3. Push the remaining de-ImGui cleanup from build/header isolation into behavior isolation: keep ImGui confined to `XCNewEditorImGuiCompat`, keep generic XCUI seams free of compat-only factories/types, and keep shrinking the legacy shell boundary.
4. Promote the current standalone native text/font path out of `new_editor`, harden its atlas invalidation/caching contract, and keep the remaining font/bootstrap ownership compatibility-only.
5. Promote the native shell chrome and card layout out of bespoke `Application` code into a shared XCUI/editor-layer shell model or authored shell document, then retire the remaining ImGui-window panel path.
6. Continue phased validation, commit, push, and plan refresh after each stable batch.

View File

@@ -61,8 +61,6 @@ public:
virtual void FreeTextureDescriptor(const UITextureRegistration& registration) = 0;
};
std::unique_ptr<IEditorHostCompositor> CreateImGuiHostCompositor();
} // namespace XCUIBackend
} // namespace Editor
} // namespace XCEngine

View File

@@ -61,7 +61,6 @@ public:
virtual void FreeTextureDescriptor(const UITextureRegistration& registration) = 0;
};
std::unique_ptr<IWindowUICompositor> CreateImGuiWindowUICompositor();
std::unique_ptr<IWindowUICompositor> CreateNativeWindowUICompositor();
} // namespace XCUIBackend

View File

@@ -13,7 +13,7 @@ namespace XCUIBackend {
class ImGuiWindowUICompositor final : public IWindowUICompositor {
public:
explicit ImGuiWindowUICompositor(
std::unique_ptr<IEditorHostCompositor> hostCompositor = CreateImGuiHostCompositor())
std::unique_ptr<IEditorHostCompositor> hostCompositor)
: m_hostCompositor(std::move(hostCompositor)) {
}
@@ -79,10 +79,6 @@ private:
std::unique_ptr<IEditorHostCompositor> m_hostCompositor;
};
inline std::unique_ptr<IWindowUICompositor> CreateImGuiWindowUICompositor() {
return std::make_unique<ImGuiWindowUICompositor>();
}
} // namespace XCUIBackend
} // namespace Editor
} // namespace XCEngine

View File

@@ -12,14 +12,11 @@ namespace Editor {
namespace XCUIBackend {
bool ConfigureLegacyImGuiHostFonts() {
ImGuiIO& io = ImGui::GetIO();
ImFont* uiFont = nullptr;
if (!BuildDefaultXCUIEditorFontAtlas(*io.Fonts, uiFont)) {
return false;
}
return ConfigureDefaultXCUIEditorFontsForCurrentContext();
}
io.FontDefault = uiFont;
return io.FontDefault != nullptr;
std::unique_ptr<IWindowUICompositor> CreateImGuiWindowUICompositor() {
return std::make_unique<ImGuiWindowUICompositor>(CreateImGuiHostCompositor());
}
std::unique_ptr<IWindowUICompositor> CreateLegacyImGuiWindowUICompositor() {

View File

@@ -1,5 +1,6 @@
#pragma once
#include "XCUIBackend/IEditorHostCompositor.h"
#include "XCUIBackend/XCUIInputBridge.h"
#include "XCUIBackend/IWindowUICompositor.h"
#include "XCUIBackend/XCUIHostedPreviewPresenter.h"
@@ -13,6 +14,10 @@ namespace XCUIBackend {
bool ConfigureLegacyImGuiHostFonts();
std::unique_ptr<IEditorHostCompositor> CreateImGuiHostCompositor();
std::unique_ptr<IWindowUICompositor> CreateImGuiWindowUICompositor();
std::unique_ptr<IWindowUICompositor> CreateLegacyImGuiWindowUICompositor();
std::unique_ptr<IXCUIHostedPreviewPresenter> CreateLegacyImGuiHostedPreviewPresenter();

View File

@@ -12,8 +12,6 @@ constexpr float kUiFontSize = 18.0f;
constexpr const char* kPrimaryUiFontPath = "C:/Windows/Fonts/segoeui.ttf";
constexpr const char* kChineseFallbackFontPath = "C:/Windows/Fonts/msyh.ttc";
} // namespace
bool BuildDefaultXCUIEditorFontAtlas(::ImFontAtlas& atlas, ::ImFont*& outDefaultFont) {
outDefaultFont = nullptr;
atlas.Clear();
@@ -55,6 +53,19 @@ bool BuildDefaultXCUIEditorFontAtlas(::ImFontAtlas& atlas, ::ImFont*& outDefault
return outDefaultFont != nullptr;
}
} // namespace
bool ConfigureDefaultXCUIEditorFontsForCurrentContext() {
ImGuiIO& io = ImGui::GetIO();
ImFont* uiFont = nullptr;
if (!BuildDefaultXCUIEditorFontAtlas(*io.Fonts, uiFont)) {
return false;
}
io.FontDefault = uiFont;
return io.FontDefault != nullptr;
}
} // namespace XCUIBackend
} // namespace Editor
} // namespace XCEngine

View File

@@ -1,13 +1,10 @@
#pragma once
struct ImFont;
struct ImFontAtlas;
namespace XCEngine {
namespace Editor {
namespace XCUIBackend {
bool BuildDefaultXCUIEditorFontAtlas(::ImFontAtlas& atlas, ::ImFont*& outDefaultFont);
bool ConfigureDefaultXCUIEditorFontsForCurrentContext();
} // namespace XCUIBackend
} // namespace Editor

View File

@@ -1,7 +1,6 @@
#pragma once
#include "IXCUITextAtlasProvider.h"
#include "XCUIEditorFontSetup.h"
#include <memory>

View File

@@ -1,5 +1,6 @@
#include <gtest/gtest.h>
#include "Platform/D3D12WindowRenderer.h"
#include "XCUIBackend/IEditorHostCompositor.h"
#include "XCUIBackend/ImGuiWindowUICompositor.h"
#include "XCUIBackend/UITextureRegistration.h"

View File

@@ -0,0 +1,78 @@
#include <gtest/gtest.h>
#include "XCUIBackend/LegacyImGuiHostInterop.h"
#include <imgui.h>
namespace {
using XCEngine::Editor::XCUIBackend::ApplyLegacyImGuiHostInputCapture;
using XCEngine::Editor::XCUIBackend::ConfigureLegacyImGuiHostFonts;
using XCEngine::Editor::XCUIBackend::CreateLegacyImGuiHostedPreviewPresenter;
using XCEngine::Editor::XCUIBackend::CreateLegacyImGuiPanelCanvasHost;
using XCEngine::Editor::XCUIBackend::CreateLegacyImGuiWindowUICompositor;
using XCEngine::Editor::XCUIBackend::RenderLegacyImGuiDemoWindow;
using XCEngine::Editor::XCUIBackend::XCUIPanelCanvasHostBackend;
using XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot;
class ScopedImGuiContext final {
public:
ScopedImGuiContext() {
IMGUI_CHECKVERSION();
ImGui::CreateContext();
}
~ScopedImGuiContext() {
ImGui::DestroyContext();
}
ScopedImGuiContext(const ScopedImGuiContext&) = delete;
ScopedImGuiContext& operator=(const ScopedImGuiContext&) = delete;
};
TEST(LegacyImGuiHostInteropTest, CreatesLegacyHostAdaptersWithImGuiBackends) {
auto compositor = CreateLegacyImGuiWindowUICompositor();
auto presenter = CreateLegacyImGuiHostedPreviewPresenter();
auto canvasHost = CreateLegacyImGuiPanelCanvasHost();
ASSERT_NE(compositor, nullptr);
ASSERT_NE(presenter, nullptr);
ASSERT_NE(canvasHost, nullptr);
EXPECT_EQ(canvasHost->GetBackend(), XCUIPanelCanvasHostBackend::ImGui);
const auto capabilities = canvasHost->GetCapabilities();
EXPECT_TRUE(capabilities.supportsPointerHitTesting);
EXPECT_TRUE(capabilities.supportsHostedSurfaceImages);
EXPECT_TRUE(capabilities.supportsPrimitiveOverlays);
}
TEST(LegacyImGuiHostInteropTest, ApplyInputCaptureCopiesImGuiIoFlagsIntoSnapshot) {
ScopedImGuiContext context = {};
ImGuiIO& io = ImGui::GetIO();
io.WantCaptureKeyboard = true;
io.WantTextInput = true;
XCUIInputBridgeFrameSnapshot snapshot = {};
ApplyLegacyImGuiHostInputCapture(snapshot);
EXPECT_TRUE(snapshot.wantCaptureKeyboard);
EXPECT_TRUE(snapshot.wantTextInput);
}
TEST(LegacyImGuiHostInteropTest, ConfigureFontsPopulatesDefaultFont) {
ScopedImGuiContext context = {};
ASSERT_TRUE(ConfigureLegacyImGuiHostFonts());
EXPECT_NE(ImGui::GetIO().FontDefault, nullptr);
EXPECT_GT(ImGui::GetIO().Fonts->Fonts.Size, 0);
}
TEST(LegacyImGuiHostInteropTest, RenderDemoWindowSkipsWorkWhenVisibilityIsDisabled) {
ScopedImGuiContext context = {};
bool visible = false;
EXPECT_FALSE(RenderLegacyImGuiDemoWindow(visible));
EXPECT_FALSE(visible);
}
} // namespace