Default new_editor to native XCUI shell host
This commit is contained in:
@@ -16,6 +16,12 @@ Old `editor` replacement is explicitly out of scope for this phase.
|
||||
- `LayoutLab` continues as the editor widget proving ground for tree/list/property-section style controls
|
||||
- the demo sandbox and editor bridge APIs were tightened again without touching the old editor replacement scope
|
||||
- `new_editor` now has an explicit two-step window compositor seam through `IWindowUICompositor` / `ImGuiWindowUICompositor`, layered on top of `IEditorHostCompositor` / `ImGuiHostCompositor`
|
||||
- The native-host follow-up is now landed in `new_editor`:
|
||||
- `NativeWindowUICompositor` is now buildable alongside the legacy ImGui compositor
|
||||
- `Application` now defaults to a native XCUI host path instead of creating the ImGui shell by default
|
||||
- the native default path now drives `XCUIDemoRuntime` and `XCUILayoutLabRuntime` directly, composes their `UIDrawData`, and submits one swapchain packet through the native compositor
|
||||
- the old ImGui shell path remains present as an explicit compatibility host instead of the default host
|
||||
- `XCUIInputBridge.h` no longer drags `imgui.h` through the public XCUI input seam
|
||||
- Old `editor` replacement remains deferred; all active execution still stays inside XCUI shared code and `new_editor`.
|
||||
|
||||
## Three-Layer Status
|
||||
@@ -93,14 +99,24 @@ Current gap:
|
||||
- `XCUIDemoRuntime` now bridges pointer activation, text-edit commands, and shortcut-triggered commands through a unified command path, and `DrainPendingCommandIds()` now preserves mixed pointer/text/shortcut ordering.
|
||||
- `new_editor` now also has a pure `XCUIShellChromeState` model covering panel visibility, hosted-preview mode, and shell-level view toggles without depending on ImGui, `Application`, or the old editor.
|
||||
- `XCUIShellChromeState` hosted-preview mode naming is now backend-neutral (`HostedPresenter` / `NativeOffscreen`) instead of encoding `ImGui` into the XCUI shell model.
|
||||
- `new_editor` now also has a concrete `NativeWindowUICompositor` path and native-focused compositor tests, so the window compositor seam is no longer ImGui-only.
|
||||
- `Application` now also has a native XCUI shell path that:
|
||||
- becomes the default `new_editor` startup path
|
||||
- lays out `XCUI Demo` and `Layout Lab` as native cards directly in the swapchain window
|
||||
- routes shell shortcuts through the same command router without reading ImGui capture state in the default host path
|
||||
- drives both sandbox runtimes directly from Win32/XCUI input snapshots instead of routing through ImGui panel hosts
|
||||
- composes one native `UIDrawData` packet and submits it through `NativeWindowUICompositor`
|
||||
- `NativeXCUIPanelCanvasHost` now backs that direct shell path as an externally driven canvas/session host for native cards instead of assuming an ImGui child-window model.
|
||||
- `XCUIInputBridge.h` no longer includes `imgui.h`, so the public XCUI input bridge seam is now host-neutral at the header boundary.
|
||||
- `XCNewEditor` builds successfully to `build/new_editor/bin/Debug/XCNewEditor.exe`.
|
||||
|
||||
Current gap:
|
||||
|
||||
- The shell is still ImGui-hosted.
|
||||
- Hosted-preview compatibility presentation still depends on an ImGui-specific inline draw target binding when the native queued surface path is disabled.
|
||||
- The panel-canvas seam still only has an ImGui adapter today; a native panel/shell host still needs to replace it before the editor shell can stop depending on ImGui host chrome.
|
||||
- Editor-specialized widgets are still incomplete at the shared-module level: the authored prototypes exist, but virtualization, multi-selection/focus traversal, toolbar/menu chrome, and icon-atlas widgets are not yet extracted into reusable XCUI modules, and broader editor-host keybinding plus full shell-state adoption are still only partially integrated.
|
||||
- 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`.
|
||||
- Hosted-preview offscreen surface publication is still not wired for the native compositor path because descriptor-backed texture registration is still ImGui-host-centric today.
|
||||
- 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.
|
||||
- Shared image support is still incomplete for full native hosted-preview parity: `UI::UIDrawData` still lacks explicit source-rect/UV-level image commands, so native surface-image composition cannot yet mirror the ImGui panel host path exactly.
|
||||
|
||||
## Validated This Phase
|
||||
|
||||
@@ -113,11 +129,13 @@ Current gap:
|
||||
- `new_editor_xcui_rhi_render_backend_tests`: `5/5`
|
||||
- `new_editor_xcui_hosted_preview_presenter_tests`: `20/20`
|
||||
- `new_editor_imgui_window_ui_compositor_tests`: `7/7`
|
||||
- `new_editor_native_window_ui_compositor_tests`: `5/5`
|
||||
- `new_editor_xcui_editor_command_router_tests`: `5/5`
|
||||
- `new_editor_application_shell_command_bindings_tests`: `6/6`
|
||||
- `new_editor_xcui_shell_chrome_state_tests`: `11/11`
|
||||
- `new_editor_xcui_panel_canvas_host_tests`: `4/4`
|
||||
- `new_editor_imgui_xcui_panel_canvas_host_tests`: `1/1`
|
||||
- `new_editor_native_xcui_panel_canvas_host_tests`: `4/4`
|
||||
- `new_editor_xcui_layout_lab_panel_tests`: `3/3`
|
||||
- `XCNewEditor` Debug target builds successfully
|
||||
- `core_ui_tests`: `52 total` (`50` passed, `2` skipped because `KeyCode::Delete` currently aliases `Backspace`)
|
||||
@@ -212,6 +230,12 @@ Current gap:
|
||||
- `XCUIShellChromeState` now also exposes effective hosted-preview state helpers and shell view-toggle command-id helpers, so shell routing code no longer has to manually combine enablement and requested preview mode.
|
||||
- `XCUIShellChromeState` hosted-preview modes were renamed away from `LegacyImGui`, so XCUI shell state no longer treats ImGui as the generic fallback concept.
|
||||
- The panel-canvas seam now has dedicated null/imgui backend coverage, including explicit backend/capability reporting and a non-ImGui placeholder host path for future native shell adoption.
|
||||
- The native host follow-up is now present in `new_editor`:
|
||||
- `NativeWindowUICompositor` provides a swapchain-native XCUI packet present path beside the legacy ImGui compositor
|
||||
- `Application` now defaults to that native host path and directly composes `XCUI Demo` plus `Layout Lab` into one native shell frame
|
||||
- `NativeXCUIPanelCanvasHost` now drives externally configured native card sessions for that shell path
|
||||
- new native compositor/native canvas-host tests now cover the new host seam
|
||||
- `XCUIInputBridge.h` no longer includes `imgui.h`, so XCUI input translation is no longer coupled to ImGui at the public header boundary.
|
||||
- `SceneRuntime` layered XCUI routing now has dedicated regression coverage for:
|
||||
- top-interactive layer input ownership
|
||||
- blocking/modal layer suppression of lower layers
|
||||
|
||||
@@ -2,13 +2,16 @@
|
||||
#include "XCUIBackend/ImGuiXCUIPanelCanvasHost.h"
|
||||
#include "XCUIBackend/ImGuiXCUIHostedPreviewPresenter.h"
|
||||
#include "XCUIBackend/ImGuiWindowUICompositor.h"
|
||||
#include "XCUIBackend/NativeWindowUICompositor.h"
|
||||
|
||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||
#include <XCEngine/UI/DrawData.h>
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <sstream>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace NewEditor {
|
||||
@@ -18,6 +21,14 @@ constexpr wchar_t kWindowClassName[] = L"XCNewEditorWindowClass";
|
||||
constexpr wchar_t kWindowTitle[] = L"XCNewEditor";
|
||||
constexpr float kClearColor[4] = { 0.08f, 0.09f, 0.11f, 1.0f };
|
||||
constexpr float kHostedPreviewClearColor[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
|
||||
constexpr float kNativeShellOuterMargin = 22.0f;
|
||||
constexpr float kNativeShellInnerGap = 18.0f;
|
||||
constexpr float kNativeShellHeaderHeight = 58.0f;
|
||||
constexpr float kNativeShellFooterHeight = 34.0f;
|
||||
constexpr float kNativePanelHeaderHeight = 42.0f;
|
||||
constexpr float kNativePanelPadding = 14.0f;
|
||||
constexpr float kNativePanelMinWidth = 260.0f;
|
||||
constexpr float kNativePanelMinHeight = 180.0f;
|
||||
|
||||
template <typename ResourceType>
|
||||
void ShutdownAndDelete(ResourceType*& resource) {
|
||||
@@ -78,6 +89,100 @@ const char* GetHostedPreviewStateLabel(
|
||||
}
|
||||
return "idle";
|
||||
}
|
||||
|
||||
bool ContainsPoint(const UI::UIRect& rect, const UI::UIPoint& point) {
|
||||
return point.x >= rect.x &&
|
||||
point.y >= rect.y &&
|
||||
point.x <= rect.x + rect.width &&
|
||||
point.y <= rect.y + rect.height;
|
||||
}
|
||||
|
||||
std::uint64_t MakeFrameTimestampNanoseconds() {
|
||||
return static_cast<std::uint64_t>(
|
||||
std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||
std::chrono::steady_clock::now().time_since_epoch())
|
||||
.count());
|
||||
}
|
||||
|
||||
void AppendDrawData(::XCEngine::UI::UIDrawData& destination, const ::XCEngine::UI::UIDrawData& source) {
|
||||
for (const ::XCEngine::UI::UIDrawList& drawList : source.GetDrawLists()) {
|
||||
destination.AddDrawList(drawList);
|
||||
}
|
||||
}
|
||||
|
||||
bool ContainsKeyTransition(
|
||||
const std::vector<std::int32_t>& keys,
|
||||
std::int32_t keyCode) {
|
||||
return std::find(keys.begin(), keys.end(), keyCode) != keys.end();
|
||||
}
|
||||
|
||||
bool ShouldCaptureKeyboardNavigation(
|
||||
const ::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession& canvasSession,
|
||||
const ::XCEngine::Editor::XCUIBackend::XCUILayoutLabFrameResult& previousFrame) {
|
||||
if (!canvasSession.validCanvas) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return canvasSession.hovered ||
|
||||
(canvasSession.windowFocused &&
|
||||
!previousFrame.stats.selectedElementId.empty());
|
||||
}
|
||||
|
||||
void PopulateKeyboardNavigationInput(
|
||||
::XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState& input,
|
||||
const ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameDelta& frameDelta,
|
||||
bool captureKeyboardNavigation) {
|
||||
if (!captureKeyboardNavigation) {
|
||||
return;
|
||||
}
|
||||
|
||||
using ::XCEngine::Input::KeyCode;
|
||||
const auto pressedThisFrame =
|
||||
[&frameDelta](KeyCode keyCode) {
|
||||
const std::int32_t code = static_cast<std::int32_t>(keyCode);
|
||||
return ContainsKeyTransition(frameDelta.keyboard.pressedKeys, code) ||
|
||||
ContainsKeyTransition(frameDelta.keyboard.repeatedKeys, code);
|
||||
};
|
||||
|
||||
input.navigatePrevious = pressedThisFrame(KeyCode::Up);
|
||||
input.navigateNext = pressedThisFrame(KeyCode::Down);
|
||||
input.navigateHome = pressedThisFrame(KeyCode::Home);
|
||||
input.navigateEnd = pressedThisFrame(KeyCode::End);
|
||||
input.navigateCollapse = pressedThisFrame(KeyCode::Left);
|
||||
input.navigateExpand = pressedThisFrame(KeyCode::Right);
|
||||
}
|
||||
|
||||
struct NativeShellPanelLayout {
|
||||
Application::ShellPanelId panelId = Application::ShellPanelId::XCUIDemo;
|
||||
std::string title = {};
|
||||
UI::UIRect panelRect = {};
|
||||
UI::UIRect canvasRect = {};
|
||||
bool visible = false;
|
||||
bool hovered = false;
|
||||
bool active = false;
|
||||
};
|
||||
|
||||
NativeShellPanelLayout MakePanelLayout(
|
||||
Application::ShellPanelId panelId,
|
||||
std::string title,
|
||||
const UI::UIRect& panelRect,
|
||||
const UI::UIPoint& pointerPosition,
|
||||
bool windowFocused,
|
||||
bool active) {
|
||||
NativeShellPanelLayout layout = {};
|
||||
layout.panelId = panelId;
|
||||
layout.title = std::move(title);
|
||||
layout.panelRect = panelRect;
|
||||
layout.canvasRect = UI::UIRect(
|
||||
panelRect.x + kNativePanelPadding,
|
||||
panelRect.y + kNativePanelHeaderHeight,
|
||||
(std::max)(0.0f, panelRect.width - kNativePanelPadding * 2.0f),
|
||||
(std::max)(0.0f, panelRect.height - kNativePanelHeaderHeight - kNativePanelPadding));
|
||||
layout.visible = panelRect.width >= kNativePanelMinWidth && panelRect.height >= kNativePanelMinHeight;
|
||||
layout.hovered = windowFocused && ContainsPoint(layout.canvasRect, pointerPosition);
|
||||
layout.active = active;
|
||||
return layout;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter>
|
||||
@@ -91,6 +196,22 @@ Application::CreateHostedPreviewPresenter(bool nativePreview) {
|
||||
return ::XCEngine::Editor::XCUIBackend::CreateImGuiXCUIHostedPreviewPresenter();
|
||||
}
|
||||
|
||||
bool Application::IsNativeWindowHostEnabled() const {
|
||||
return m_windowHostMode == WindowHostMode::NativeXCUI;
|
||||
}
|
||||
|
||||
void Application::InitializeNativeShell() {
|
||||
m_nativeActivePanel = m_shellChromeState.IsPanelVisible(ShellPanelId::XCUIDemo)
|
||||
? ShellPanelId::XCUIDemo
|
||||
: ShellPanelId::XCUILayoutLab;
|
||||
m_nativeDemoInputBridge.Reset();
|
||||
m_nativeLayoutInputBridge.Reset();
|
||||
m_nativeDemoCanvasHost.ClearCanvasSession();
|
||||
m_nativeLayoutCanvasHost.ClearCanvasSession();
|
||||
m_nativeDemoReloadSucceeded = m_nativeDemoRuntime.ReloadDocuments();
|
||||
m_nativeLayoutReloadSucceeded = m_nativeLayoutRuntime.ReloadDocuments();
|
||||
}
|
||||
|
||||
const Application::ShellPanelChromeState* Application::TryGetShellPanelState(ShellPanelId panelId) const {
|
||||
return m_shellChromeState.TryGetPanelState(panelId);
|
||||
}
|
||||
@@ -197,20 +318,25 @@ void Application::ConfigureShellCommandRouter() {
|
||||
Application::RegisterShellViewCommands(m_shellCommandRouter, bindings);
|
||||
}
|
||||
|
||||
void Application::DispatchShellShortcuts() {
|
||||
::XCEngine::Editor::XCUIBackend::XCUIInputBridgeCaptureOptions options = {};
|
||||
::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot snapshot =
|
||||
m_xcuiInputSource.CaptureSnapshot(options);
|
||||
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
snapshot.wantCaptureKeyboard = io.WantCaptureKeyboard;
|
||||
snapshot.wantTextInput = io.WantTextInput;
|
||||
::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameDelta
|
||||
Application::DispatchShellShortcuts(
|
||||
const ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot& snapshot) {
|
||||
::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot shellSnapshot = snapshot;
|
||||
if (!IsNativeWindowHostEnabled()) {
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
shellSnapshot.wantCaptureKeyboard = io.WantCaptureKeyboard;
|
||||
shellSnapshot.wantTextInput = io.WantTextInput;
|
||||
}
|
||||
|
||||
if (!m_shellInputBridge.HasBaseline()) {
|
||||
m_shellInputBridge.Prime(shellSnapshot);
|
||||
}
|
||||
const ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameDelta frameDelta =
|
||||
m_shellInputBridge.Translate(snapshot);
|
||||
m_shellInputBridge.Translate(shellSnapshot);
|
||||
const ::XCEngine::Editor::XCUIBackend::XCUIEditorCommandInputSnapshot commandSnapshot =
|
||||
Application::BuildShellShortcutSnapshot(frameDelta);
|
||||
m_shellCommandRouter.InvokeMatchingShortcut({ &commandSnapshot });
|
||||
return frameDelta;
|
||||
}
|
||||
|
||||
Application::HostedPreviewPanelDiagnostics Application::BuildHostedPreviewPanelDiagnostics(
|
||||
@@ -293,15 +419,19 @@ int Application::Run(HINSTANCE instance, int nCmdShow) {
|
||||
}
|
||||
|
||||
InitializeWindowCompositor();
|
||||
m_demoPanel = std::make_unique<XCUIDemoPanel>(
|
||||
&m_xcuiInputSource,
|
||||
CreateHostedPreviewPresenter(IsNativeHostedPreviewEnabled(ShellPanelId::XCUIDemo)),
|
||||
::XCEngine::Editor::XCUIBackend::CreateImGuiXCUIPanelCanvasHost());
|
||||
m_layoutLabPanel = std::make_unique<XCUILayoutLabPanel>(
|
||||
&m_xcuiInputSource,
|
||||
CreateHostedPreviewPresenter(IsNativeHostedPreviewEnabled(ShellPanelId::XCUILayoutLab)),
|
||||
::XCEngine::Editor::XCUIBackend::CreateImGuiXCUIPanelCanvasHost());
|
||||
ConfigureHostedPreviewPresenters();
|
||||
if (IsNativeWindowHostEnabled()) {
|
||||
InitializeNativeShell();
|
||||
} else {
|
||||
m_demoPanel = std::make_unique<XCUIDemoPanel>(
|
||||
&m_xcuiInputSource,
|
||||
CreateHostedPreviewPresenter(IsNativeHostedPreviewEnabled(ShellPanelId::XCUIDemo)),
|
||||
::XCEngine::Editor::XCUIBackend::CreateImGuiXCUIPanelCanvasHost());
|
||||
m_layoutLabPanel = std::make_unique<XCUILayoutLabPanel>(
|
||||
&m_xcuiInputSource,
|
||||
CreateHostedPreviewPresenter(IsNativeHostedPreviewEnabled(ShellPanelId::XCUILayoutLab)),
|
||||
::XCEngine::Editor::XCUIBackend::CreateImGuiXCUIPanelCanvasHost());
|
||||
ConfigureHostedPreviewPresenters();
|
||||
}
|
||||
m_shellInputBridge.Reset();
|
||||
ConfigureShellCommandRouter();
|
||||
m_running = true;
|
||||
@@ -433,7 +563,9 @@ bool Application::InitializeRenderer() {
|
||||
}
|
||||
|
||||
void Application::InitializeWindowCompositor() {
|
||||
m_windowCompositor = ::XCEngine::Editor::XCUIBackend::CreateImGuiWindowUICompositor();
|
||||
m_windowCompositor = IsNativeWindowHostEnabled()
|
||||
? ::XCEngine::Editor::XCUIBackend::CreateNativeWindowUICompositor()
|
||||
: ::XCEngine::Editor::XCUIBackend::CreateImGuiWindowUICompositor();
|
||||
if (m_windowCompositor != nullptr) {
|
||||
m_windowCompositor->Initialize(
|
||||
m_hwnd,
|
||||
@@ -654,6 +786,525 @@ bool Application::RenderHostedPreviewOffscreenSurface(
|
||||
return true;
|
||||
}
|
||||
|
||||
::XCEngine::UI::UIDrawData Application::BuildNativeShellDrawData(
|
||||
const ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot& shellSnapshot,
|
||||
const ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameDelta& shellFrameDelta) {
|
||||
::XCEngine::UI::UIDrawData composedDrawData = {};
|
||||
|
||||
RECT clientRect = {};
|
||||
if (!GetClientRect(m_hwnd, &clientRect)) {
|
||||
return composedDrawData;
|
||||
}
|
||||
|
||||
const float windowWidth = static_cast<float>(clientRect.right - clientRect.left);
|
||||
const float windowHeight = static_cast<float>(clientRect.bottom - clientRect.top);
|
||||
if (windowWidth <= 1.0f || windowHeight <= 1.0f) {
|
||||
return composedDrawData;
|
||||
}
|
||||
|
||||
const UI::UIRect shellRect(0.0f, 0.0f, windowWidth, windowHeight);
|
||||
const UI::UIColor shellBorderColor(40.0f / 255.0f, 54.0f / 255.0f, 74.0f / 255.0f, 1.0f);
|
||||
const UI::UIColor shellSurfaceColor(11.0f / 255.0f, 15.0f / 255.0f, 22.0f / 255.0f, 180.0f / 255.0f);
|
||||
const UI::UIColor panelSurfaceColor(9.0f / 255.0f, 13.0f / 255.0f, 18.0f / 255.0f, 212.0f / 255.0f);
|
||||
const UI::UIColor panelBorderColor(53.0f / 255.0f, 72.0f / 255.0f, 96.0f / 255.0f, 1.0f);
|
||||
const UI::UIColor panelAccentColor(84.0f / 255.0f, 176.0f / 255.0f, 244.0f / 255.0f, 1.0f);
|
||||
const UI::UIColor hoveredAccentColor(1.0f, 206.0f / 255.0f, 112.0f / 255.0f, 1.0f);
|
||||
const UI::UIColor textPrimary(232.0f / 255.0f, 238.0f / 255.0f, 246.0f / 255.0f, 1.0f);
|
||||
const UI::UIColor textSecondary(150.0f / 255.0f, 164.0f / 255.0f, 184.0f / 255.0f, 1.0f);
|
||||
const UI::UIColor textMuted(108.0f / 255.0f, 123.0f / 255.0f, 145.0f / 255.0f, 1.0f);
|
||||
|
||||
const float topBarY = kNativeShellOuterMargin;
|
||||
const UI::UIRect topBarRect(
|
||||
kNativeShellOuterMargin,
|
||||
topBarY,
|
||||
(std::max)(0.0f, windowWidth - kNativeShellOuterMargin * 2.0f),
|
||||
kNativeShellHeaderHeight);
|
||||
const UI::UIRect footerRect(
|
||||
kNativeShellOuterMargin,
|
||||
(std::max)(topBarRect.y + topBarRect.height + kNativeShellInnerGap, windowHeight - kNativeShellOuterMargin - kNativeShellFooterHeight),
|
||||
(std::max)(0.0f, windowWidth - kNativeShellOuterMargin * 2.0f),
|
||||
kNativeShellFooterHeight);
|
||||
|
||||
const float workspaceTop = topBarRect.y + topBarRect.height + kNativeShellInnerGap;
|
||||
const float workspaceBottom = footerRect.y - kNativeShellInnerGap;
|
||||
const UI::UIRect workspaceRect(
|
||||
kNativeShellOuterMargin,
|
||||
workspaceTop,
|
||||
(std::max)(0.0f, windowWidth - kNativeShellOuterMargin * 2.0f),
|
||||
(std::max)(0.0f, workspaceBottom - workspaceTop));
|
||||
|
||||
bool demoVisible = m_shellChromeState.IsPanelVisible(ShellPanelId::XCUIDemo);
|
||||
bool layoutVisible = m_shellChromeState.IsPanelVisible(ShellPanelId::XCUILayoutLab);
|
||||
if (m_nativeActivePanel == ShellPanelId::XCUIDemo && !demoVisible && layoutVisible) {
|
||||
m_nativeActivePanel = ShellPanelId::XCUILayoutLab;
|
||||
} else if (m_nativeActivePanel == ShellPanelId::XCUILayoutLab && !layoutVisible && demoVisible) {
|
||||
m_nativeActivePanel = ShellPanelId::XCUIDemo;
|
||||
}
|
||||
|
||||
std::vector<NativeShellPanelLayout> panelLayouts = {};
|
||||
panelLayouts.reserve(2u);
|
||||
if (demoVisible && layoutVisible) {
|
||||
const float leftWidth = (std::max)(
|
||||
kNativePanelMinWidth,
|
||||
(std::min)(workspaceRect.width * 0.60f, workspaceRect.width - kNativePanelMinWidth - kNativeShellInnerGap));
|
||||
const float rightWidth = (std::max)(0.0f, workspaceRect.width - leftWidth - kNativeShellInnerGap);
|
||||
panelLayouts.push_back(MakePanelLayout(
|
||||
ShellPanelId::XCUIDemo,
|
||||
"XCUI Demo",
|
||||
UI::UIRect(workspaceRect.x, workspaceRect.y, leftWidth, workspaceRect.height),
|
||||
shellSnapshot.pointerPosition,
|
||||
shellSnapshot.windowFocused,
|
||||
m_nativeActivePanel == ShellPanelId::XCUIDemo));
|
||||
panelLayouts.push_back(MakePanelLayout(
|
||||
ShellPanelId::XCUILayoutLab,
|
||||
"XCUI Layout Lab",
|
||||
UI::UIRect(workspaceRect.x + leftWidth + kNativeShellInnerGap, workspaceRect.y, rightWidth, workspaceRect.height),
|
||||
shellSnapshot.pointerPosition,
|
||||
shellSnapshot.windowFocused,
|
||||
m_nativeActivePanel == ShellPanelId::XCUILayoutLab));
|
||||
} else if (demoVisible) {
|
||||
panelLayouts.push_back(MakePanelLayout(
|
||||
ShellPanelId::XCUIDemo,
|
||||
"XCUI Demo",
|
||||
workspaceRect,
|
||||
shellSnapshot.pointerPosition,
|
||||
shellSnapshot.windowFocused,
|
||||
true));
|
||||
m_nativeActivePanel = ShellPanelId::XCUIDemo;
|
||||
} else if (layoutVisible) {
|
||||
panelLayouts.push_back(MakePanelLayout(
|
||||
ShellPanelId::XCUILayoutLab,
|
||||
"XCUI Layout Lab",
|
||||
workspaceRect,
|
||||
shellSnapshot.pointerPosition,
|
||||
shellSnapshot.windowFocused,
|
||||
true));
|
||||
m_nativeActivePanel = ShellPanelId::XCUILayoutLab;
|
||||
}
|
||||
|
||||
if (shellFrameDelta.pointer.pressed[0]) {
|
||||
for (const NativeShellPanelLayout& panelLayout : panelLayouts) {
|
||||
if (panelLayout.hovered) {
|
||||
m_nativeActivePanel = panelLayout.panelId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (NativeShellPanelLayout& panelLayout : panelLayouts) {
|
||||
panelLayout.active = panelLayout.panelId == m_nativeActivePanel;
|
||||
}
|
||||
}
|
||||
|
||||
::XCEngine::UI::UIDrawList& chromeBackground =
|
||||
composedDrawData.EmplaceDrawList("XCUI.NativeShell.Background");
|
||||
chromeBackground.AddFilledRect(shellRect, shellSurfaceColor, 0.0f);
|
||||
chromeBackground.AddFilledRect(topBarRect, UI::UIColor(12.0f / 255.0f, 18.0f / 255.0f, 26.0f / 255.0f, 230.0f / 255.0f), 14.0f);
|
||||
chromeBackground.AddRectOutline(topBarRect, shellBorderColor, 1.0f, 14.0f);
|
||||
chromeBackground.AddFilledRect(footerRect, UI::UIColor(12.0f / 255.0f, 18.0f / 255.0f, 26.0f / 255.0f, 214.0f / 255.0f), 12.0f);
|
||||
chromeBackground.AddRectOutline(footerRect, shellBorderColor, 1.0f, 12.0f);
|
||||
|
||||
if (panelLayouts.empty()) {
|
||||
const UI::UIRect emptyStateRect(
|
||||
workspaceRect.x,
|
||||
workspaceRect.y,
|
||||
workspaceRect.width,
|
||||
(std::max)(0.0f, workspaceRect.height));
|
||||
chromeBackground.AddFilledRect(emptyStateRect, panelSurfaceColor, 18.0f);
|
||||
chromeBackground.AddRectOutline(emptyStateRect, panelBorderColor, 1.0f, 18.0f);
|
||||
|
||||
::XCEngine::UI::UIDrawList& emptyForeground =
|
||||
composedDrawData.EmplaceDrawList("XCUI.NativeShell.EmptyState");
|
||||
emptyForeground.AddText(
|
||||
UI::UIPoint(emptyStateRect.x + 24.0f, emptyStateRect.y + 28.0f),
|
||||
"XCUI native shell is active, but both sandbox panels are hidden.",
|
||||
textPrimary);
|
||||
emptyForeground.AddText(
|
||||
UI::UIPoint(emptyStateRect.x + 24.0f, emptyStateRect.y + 50.0f),
|
||||
"Use Ctrl+1 or Ctrl+2 to bring a panel back.",
|
||||
textSecondary);
|
||||
return composedDrawData;
|
||||
}
|
||||
|
||||
for (const NativeShellPanelLayout& panelLayout : panelLayouts) {
|
||||
const UI::UIColor borderColor = panelLayout.active
|
||||
? panelAccentColor
|
||||
: (panelLayout.hovered ? hoveredAccentColor : panelBorderColor);
|
||||
chromeBackground.AddFilledRect(panelLayout.panelRect, panelSurfaceColor, 18.0f);
|
||||
chromeBackground.AddRectOutline(panelLayout.panelRect, borderColor, panelLayout.active ? 2.0f : 1.0f, 18.0f);
|
||||
chromeBackground.AddFilledRect(
|
||||
UI::UIRect(
|
||||
panelLayout.panelRect.x,
|
||||
panelLayout.panelRect.y,
|
||||
panelLayout.panelRect.width,
|
||||
kNativePanelHeaderHeight),
|
||||
UI::UIColor(13.0f / 255.0f, 20.0f / 255.0f, 28.0f / 255.0f, 242.0f / 255.0f),
|
||||
18.0f);
|
||||
}
|
||||
|
||||
struct NativePanelFrameSummary {
|
||||
NativeShellPanelLayout layout = {};
|
||||
std::string lineA = {};
|
||||
std::string lineB = {};
|
||||
::XCEngine::UI::UIDrawData overlay = {};
|
||||
};
|
||||
|
||||
std::vector<NativePanelFrameSummary> panelSummaries = {};
|
||||
panelSummaries.reserve(panelLayouts.size());
|
||||
|
||||
const auto capturePanelSnapshot =
|
||||
[this, &shellSnapshot](const NativeShellPanelLayout& panelLayout, bool wantsKeyboard) {
|
||||
::XCEngine::Editor::XCUIBackend::XCUIInputBridgeCaptureOptions options = {};
|
||||
options.timestampNanoseconds = shellSnapshot.timestampNanoseconds;
|
||||
options.windowFocused = shellSnapshot.windowFocused;
|
||||
options.hasPointerInsideOverride = true;
|
||||
options.pointerInsideOverride = panelLayout.hovered;
|
||||
|
||||
::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot panelSnapshot =
|
||||
m_xcuiInputSource.CaptureSnapshot(options);
|
||||
if (!wantsKeyboard) {
|
||||
panelSnapshot.keys.clear();
|
||||
panelSnapshot.characters.clear();
|
||||
panelSnapshot.wantCaptureKeyboard = false;
|
||||
panelSnapshot.wantTextInput = false;
|
||||
}
|
||||
return panelSnapshot;
|
||||
};
|
||||
|
||||
const auto extractCanvasOverlay =
|
||||
[](const ::XCEngine::Editor::XCUIBackend::NativeXCUIPanelCanvasHost& canvasHost) {
|
||||
::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasFrameSnapshot snapshot = {};
|
||||
if (!canvasHost.TryGetLatestFrameSnapshot(snapshot)) {
|
||||
return ::XCEngine::UI::UIDrawData();
|
||||
}
|
||||
return snapshot.overlayDrawData;
|
||||
};
|
||||
|
||||
for (const NativeShellPanelLayout& panelLayout : panelLayouts) {
|
||||
if (panelLayout.panelId == ShellPanelId::XCUIDemo) {
|
||||
::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession canvasSession = {};
|
||||
canvasSession.hostRect = panelLayout.panelRect;
|
||||
canvasSession.canvasRect = panelLayout.canvasRect;
|
||||
canvasSession.pointerPosition = shellSnapshot.pointerPosition;
|
||||
canvasSession.validCanvas = panelLayout.canvasRect.width > 1.0f && panelLayout.canvasRect.height > 1.0f;
|
||||
canvasSession.hovered = panelLayout.hovered;
|
||||
canvasSession.windowFocused = shellSnapshot.windowFocused;
|
||||
m_nativeDemoCanvasHost.SetCanvasSession(canvasSession);
|
||||
|
||||
::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasRequest canvasRequest = {};
|
||||
canvasRequest.childId = "XCUIDemo.NativeCanvas";
|
||||
canvasRequest.height = panelLayout.panelRect.height;
|
||||
canvasRequest.topInset = panelLayout.canvasRect.y - panelLayout.panelRect.y;
|
||||
canvasRequest.drawPreviewFrame = false;
|
||||
const ::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession resolvedSession =
|
||||
m_nativeDemoCanvasHost.BeginCanvas(canvasRequest);
|
||||
|
||||
const bool wantsKeyboard = panelLayout.active;
|
||||
const auto panelSnapshot = capturePanelSnapshot(panelLayout, wantsKeyboard);
|
||||
if (!m_nativeDemoInputBridge.HasBaseline()) {
|
||||
m_nativeDemoInputBridge.Prime(panelSnapshot);
|
||||
}
|
||||
const auto panelFrameDelta = m_nativeDemoInputBridge.Translate(panelSnapshot);
|
||||
|
||||
::XCEngine::Editor::XCUIBackend::XCUIDemoInputState input = {};
|
||||
input.canvasRect = resolvedSession.canvasRect;
|
||||
input.pointerPosition = panelSnapshot.pointerPosition;
|
||||
input.pointerInside = panelSnapshot.pointerInside;
|
||||
input.pointerPressed = panelFrameDelta.pointer.pressed[0];
|
||||
input.pointerReleased = panelFrameDelta.pointer.released[0];
|
||||
input.pointerDown = panelSnapshot.pointerButtonsDown[0];
|
||||
input.windowFocused = panelSnapshot.windowFocused;
|
||||
input.wantCaptureMouse = panelSnapshot.wantCaptureMouse;
|
||||
input.wantCaptureKeyboard = panelSnapshot.wantCaptureKeyboard;
|
||||
input.wantTextInput = panelSnapshot.wantTextInput;
|
||||
input.events = panelFrameDelta.events;
|
||||
|
||||
const auto& frame = m_nativeDemoRuntime.Update(input);
|
||||
AppendDrawData(composedDrawData, frame.drawData);
|
||||
|
||||
if (IsShellViewToggleEnabled(ShellViewToggleId::HostedPreviewHud)) {
|
||||
const auto& stats = frame.stats;
|
||||
const UI::UIRect hudRect(
|
||||
resolvedSession.canvasRect.x + 10.0f,
|
||||
resolvedSession.canvasRect.y + 10.0f,
|
||||
(std::min)(resolvedSession.canvasRect.width - 20.0f, 360.0f),
|
||||
62.0f);
|
||||
if (hudRect.width > 40.0f && hudRect.height > 20.0f) {
|
||||
m_nativeDemoCanvasHost.DrawFilledRect(
|
||||
hudRect,
|
||||
UI::UIColor(12.0f / 255.0f, 18.0f / 255.0f, 26.0f / 255.0f, 214.0f / 255.0f),
|
||||
10.0f);
|
||||
m_nativeDemoCanvasHost.DrawOutlineRect(hudRect, panelBorderColor, 1.0f, 10.0f);
|
||||
m_nativeDemoCanvasHost.DrawText(
|
||||
UI::UIPoint(hudRect.x + 10.0f, hudRect.y + 8.0f),
|
||||
"Direct native XCUI frame",
|
||||
textPrimary);
|
||||
m_nativeDemoCanvasHost.DrawText(
|
||||
UI::UIPoint(hudRect.x + 10.0f, hudRect.y + 28.0f),
|
||||
std::string("Tree ") + std::to_string(static_cast<unsigned long long>(stats.treeGeneration)) +
|
||||
" | Elements " + std::to_string(stats.elementCount) +
|
||||
" | Commands " + std::to_string(stats.commandCount),
|
||||
textSecondary);
|
||||
m_nativeDemoCanvasHost.DrawText(
|
||||
UI::UIPoint(hudRect.x + 10.0f, hudRect.y + 46.0f),
|
||||
stats.statusMessage,
|
||||
textMuted);
|
||||
}
|
||||
|
||||
const auto drawDebugRect =
|
||||
[this, &stats](const std::string& elementId, const UI::UIColor& color, const char* label) {
|
||||
if (elementId.empty()) {
|
||||
return;
|
||||
}
|
||||
UI::UIRect rect = {};
|
||||
if (!m_nativeDemoRuntime.TryGetElementRect(elementId, rect)) {
|
||||
return;
|
||||
}
|
||||
m_nativeDemoCanvasHost.DrawOutlineRect(rect, color, 2.0f, 6.0f);
|
||||
if (label != nullptr && label[0] != '\0') {
|
||||
m_nativeDemoCanvasHost.DrawText(UI::UIPoint(rect.x + 4.0f, rect.y + 4.0f), label, color);
|
||||
}
|
||||
};
|
||||
drawDebugRect(stats.hoveredElementId, UI::UIColor(1.0f, 195.0f / 255.0f, 64.0f / 255.0f, 1.0f), "hover");
|
||||
drawDebugRect(stats.focusedElementId, UI::UIColor(64.0f / 255.0f, 214.0f / 255.0f, 1.0f, 1.0f), "focus");
|
||||
}
|
||||
|
||||
m_nativeDemoCanvasHost.EndCanvas();
|
||||
|
||||
NativePanelFrameSummary summary = {};
|
||||
summary.layout = panelLayout;
|
||||
summary.lineA = m_nativeDemoReloadSucceeded
|
||||
? frame.stats.statusMessage
|
||||
: "Document reload failed; showing last retained runtime state.";
|
||||
summary.lineB =
|
||||
std::string(panelLayout.active ? "Active" : "Passive") +
|
||||
" | " + std::to_string(frame.stats.elementCount) +
|
||||
" elements | " + std::to_string(frame.stats.commandCount) +
|
||||
" cmds";
|
||||
summary.overlay = extractCanvasOverlay(m_nativeDemoCanvasHost);
|
||||
panelSummaries.push_back(std::move(summary));
|
||||
m_nativeDemoCanvasHost.ClearCanvasSession();
|
||||
continue;
|
||||
}
|
||||
|
||||
::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession canvasSession = {};
|
||||
canvasSession.hostRect = panelLayout.panelRect;
|
||||
canvasSession.canvasRect = panelLayout.canvasRect;
|
||||
canvasSession.pointerPosition = shellSnapshot.pointerPosition;
|
||||
canvasSession.validCanvas = panelLayout.canvasRect.width > 1.0f && panelLayout.canvasRect.height > 1.0f;
|
||||
canvasSession.hovered = panelLayout.hovered;
|
||||
canvasSession.windowFocused = shellSnapshot.windowFocused;
|
||||
m_nativeLayoutCanvasHost.SetCanvasSession(canvasSession);
|
||||
|
||||
::XCEngine::Editor::XCUIBackend::XCUIPanelCanvasRequest canvasRequest = {};
|
||||
canvasRequest.childId = "XCUILayoutLab.NativeCanvas";
|
||||
canvasRequest.height = panelLayout.panelRect.height;
|
||||
canvasRequest.topInset = panelLayout.canvasRect.y - panelLayout.panelRect.y;
|
||||
canvasRequest.drawPreviewFrame = false;
|
||||
const auto resolvedSession = m_nativeLayoutCanvasHost.BeginCanvas(canvasRequest);
|
||||
|
||||
const bool wantsKeyboard = panelLayout.active;
|
||||
const auto panelSnapshot = capturePanelSnapshot(panelLayout, wantsKeyboard);
|
||||
if (!m_nativeLayoutInputBridge.HasBaseline()) {
|
||||
m_nativeLayoutInputBridge.Prime(panelSnapshot);
|
||||
}
|
||||
const auto panelFrameDelta = m_nativeLayoutInputBridge.Translate(panelSnapshot);
|
||||
|
||||
::XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState input = {};
|
||||
input.canvasRect = resolvedSession.canvasRect;
|
||||
input.pointerPosition = panelSnapshot.pointerPosition;
|
||||
input.pointerInside = panelSnapshot.pointerInside;
|
||||
input.pointerPressed = input.pointerInside && panelFrameDelta.pointer.pressed[0];
|
||||
PopulateKeyboardNavigationInput(
|
||||
input,
|
||||
panelFrameDelta,
|
||||
panelLayout.active && ShouldCaptureKeyboardNavigation(resolvedSession, m_nativeLayoutRuntime.GetFrameResult()));
|
||||
|
||||
const auto& frame = m_nativeLayoutRuntime.Update(input);
|
||||
AppendDrawData(composedDrawData, frame.drawData);
|
||||
m_nativeLayoutCanvasHost.EndCanvas();
|
||||
|
||||
NativePanelFrameSummary summary = {};
|
||||
summary.layout = panelLayout;
|
||||
summary.lineA = m_nativeLayoutReloadSucceeded
|
||||
? frame.stats.statusMessage
|
||||
: "Layout lab reload failed; showing last retained runtime state.";
|
||||
summary.lineB =
|
||||
std::to_string(frame.stats.rowCount) + " rows | " +
|
||||
std::to_string(frame.stats.columnCount) + " cols | " +
|
||||
std::to_string(frame.stats.commandCount) + " cmds";
|
||||
summary.overlay = extractCanvasOverlay(m_nativeLayoutCanvasHost);
|
||||
panelSummaries.push_back(std::move(summary));
|
||||
m_nativeLayoutCanvasHost.ClearCanvasSession();
|
||||
}
|
||||
|
||||
::XCEngine::UI::UIDrawList& chromeForeground =
|
||||
composedDrawData.EmplaceDrawList("XCUI.NativeShell.Foreground");
|
||||
chromeForeground.AddText(
|
||||
UI::UIPoint(topBarRect.x + 18.0f, topBarRect.y + 14.0f),
|
||||
"XCUI Native Shell",
|
||||
textPrimary);
|
||||
chromeForeground.AddText(
|
||||
UI::UIPoint(topBarRect.x + 18.0f, topBarRect.y + 34.0f),
|
||||
"Default host path is now direct XCUI composition over the swapchain, with ImGui kept only as an explicit compatibility shell.",
|
||||
textSecondary);
|
||||
|
||||
std::ostringstream footerStream = {};
|
||||
footerStream
|
||||
<< "Ctrl+1 Demo | Ctrl+2 Layout | Ctrl+Shift+B Backdrop "
|
||||
<< "| Ctrl+Shift+H HUD | Active panel: "
|
||||
<< (m_nativeActivePanel == ShellPanelId::XCUIDemo ? "XCUI Demo" : "XCUI Layout Lab");
|
||||
chromeForeground.AddText(
|
||||
UI::UIPoint(footerRect.x + 14.0f, footerRect.y + 10.0f),
|
||||
footerStream.str(),
|
||||
textSecondary);
|
||||
|
||||
for (const NativePanelFrameSummary& summary : panelSummaries) {
|
||||
chromeForeground.AddText(
|
||||
UI::UIPoint(summary.layout.panelRect.x + 16.0f, summary.layout.panelRect.y + 12.0f),
|
||||
summary.layout.title,
|
||||
textPrimary);
|
||||
chromeForeground.AddText(
|
||||
UI::UIPoint(summary.layout.panelRect.x + 16.0f, summary.layout.panelRect.y + 28.0f),
|
||||
summary.lineA,
|
||||
textSecondary);
|
||||
chromeForeground.AddText(
|
||||
UI::UIPoint(summary.layout.panelRect.x + 16.0f, summary.layout.panelRect.y + summary.layout.panelRect.height - 18.0f),
|
||||
summary.lineB,
|
||||
textMuted);
|
||||
}
|
||||
|
||||
if (IsShellViewToggleEnabled(ShellViewToggleId::NativeXCUIOverlay)) {
|
||||
const UI::UIRect overlayRect(
|
||||
topBarRect.x + topBarRect.width - 282.0f,
|
||||
topBarRect.y + 10.0f,
|
||||
266.0f,
|
||||
38.0f);
|
||||
chromeForeground.AddFilledRect(
|
||||
overlayRect,
|
||||
UI::UIColor(18.0f / 255.0f, 72.0f / 255.0f, 112.0f / 255.0f, 196.0f / 255.0f),
|
||||
10.0f);
|
||||
chromeForeground.AddRectOutline(overlayRect, panelAccentColor, 1.0f, 10.0f);
|
||||
chromeForeground.AddText(
|
||||
UI::UIPoint(overlayRect.x + 12.0f, overlayRect.y + 12.0f),
|
||||
"Overlay: native compositor + direct XCUI packet",
|
||||
textPrimary);
|
||||
}
|
||||
|
||||
for (const NativePanelFrameSummary& summary : panelSummaries) {
|
||||
AppendDrawData(composedDrawData, summary.overlay);
|
||||
}
|
||||
|
||||
return composedDrawData;
|
||||
}
|
||||
|
||||
void Application::FrameLegacyImGuiHost() {
|
||||
m_hostedPreviewQueue.BeginFrame();
|
||||
m_hostedPreviewSurfaceRegistry.BeginFrame();
|
||||
SyncHostedPreviewSurfaces();
|
||||
if (m_windowCompositor == nullptr) {
|
||||
m_xcuiInputSource.ClearFrameTransients();
|
||||
return;
|
||||
}
|
||||
|
||||
m_windowCompositor->RenderFrame(
|
||||
kClearColor,
|
||||
[this]() {
|
||||
::XCEngine::Editor::XCUIBackend::XCUIInputBridgeCaptureOptions options = {};
|
||||
options.timestampNanoseconds = MakeFrameTimestampNanoseconds();
|
||||
options.windowFocused = m_xcuiInputSource.IsWindowFocused();
|
||||
const auto shellSnapshot = m_xcuiInputSource.CaptureSnapshot(options);
|
||||
DispatchShellShortcuts(shellSnapshot);
|
||||
RenderShellChrome();
|
||||
if (m_demoPanel) {
|
||||
m_demoPanel->RenderIfVisible();
|
||||
}
|
||||
if (m_layoutLabPanel) {
|
||||
m_layoutLabPanel->RenderIfVisible();
|
||||
}
|
||||
bool showImGuiDemoWindow = IsShellViewToggleEnabled(ShellViewToggleId::ImGuiDemoWindow);
|
||||
if (showImGuiDemoWindow) {
|
||||
ImGui::ShowDemoWindow(&showImGuiDemoWindow);
|
||||
SetShellViewToggleEnabled(ShellViewToggleId::ImGuiDemoWindow, showImGuiDemoWindow);
|
||||
}
|
||||
|
||||
SyncShellChromePanelStateFromPanels();
|
||||
SyncHostedPreviewSurfaces();
|
||||
},
|
||||
[this](
|
||||
const ::XCEngine::Rendering::RenderContext& renderContext,
|
||||
const ::XCEngine::Rendering::RenderSurface& surface) {
|
||||
RenderQueuedHostedPreviews(renderContext, surface);
|
||||
|
||||
if (!IsShellViewToggleEnabled(ShellViewToggleId::NativeBackdrop) &&
|
||||
!IsShellViewToggleEnabled(ShellViewToggleId::NativeXCUIOverlay)) {
|
||||
return;
|
||||
}
|
||||
|
||||
MainWindowNativeBackdropRenderer::FrameState frameState = {};
|
||||
frameState.elapsedSeconds = static_cast<float>(
|
||||
std::chrono::duration<double>(std::chrono::steady_clock::now() - m_startTime).count());
|
||||
frameState.pulseAccent = IsShellViewToggleEnabled(ShellViewToggleId::PulseAccent);
|
||||
frameState.drawBackdrop = IsShellViewToggleEnabled(ShellViewToggleId::NativeBackdrop);
|
||||
if (IsShellViewToggleEnabled(ShellViewToggleId::NativeXCUIOverlay)) {
|
||||
const float width = static_cast<float>(surface.GetWidth());
|
||||
const float height = static_cast<float>(surface.GetHeight());
|
||||
const float horizontalMargin = (std::min)(width * 0.14f, 128.0f);
|
||||
const float topMargin = (std::min)(height * 0.15f, 132.0f);
|
||||
const float bottomMargin = (std::min)(height * 0.12f, 96.0f);
|
||||
|
||||
::XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState overlayInput = {};
|
||||
overlayInput.canvasRect = ::XCEngine::UI::UIRect(
|
||||
horizontalMargin,
|
||||
topMargin,
|
||||
(std::max)(0.0f, width - horizontalMargin * 2.0f),
|
||||
(std::max)(0.0f, height - topMargin - bottomMargin));
|
||||
overlayInput.pointerPosition = m_xcuiInputSource.GetPointerPosition();
|
||||
overlayInput.pointerInside =
|
||||
overlayInput.pointerPosition.x >= overlayInput.canvasRect.x &&
|
||||
overlayInput.pointerPosition.y >= overlayInput.canvasRect.y &&
|
||||
overlayInput.pointerPosition.x <= overlayInput.canvasRect.x + overlayInput.canvasRect.width &&
|
||||
overlayInput.pointerPosition.y <= overlayInput.canvasRect.y + overlayInput.canvasRect.height;
|
||||
const auto& overlayFrame = m_nativeOverlayRuntime.Update(overlayInput);
|
||||
frameState.overlayDrawData = &overlayFrame.drawData;
|
||||
}
|
||||
m_nativeBackdropRenderer.Render(renderContext, surface, frameState);
|
||||
});
|
||||
|
||||
m_xcuiInputSource.ClearFrameTransients();
|
||||
}
|
||||
|
||||
void Application::FrameNativeXCUIHost() {
|
||||
auto* nativeCompositor =
|
||||
dynamic_cast<::XCEngine::Editor::XCUIBackend::NativeWindowUICompositor*>(m_windowCompositor.get());
|
||||
if (nativeCompositor == nullptr) {
|
||||
m_xcuiInputSource.ClearFrameTransients();
|
||||
return;
|
||||
}
|
||||
|
||||
::XCEngine::Editor::XCUIBackend::XCUIInputBridgeCaptureOptions options = {};
|
||||
options.timestampNanoseconds = MakeFrameTimestampNanoseconds();
|
||||
options.windowFocused = m_xcuiInputSource.IsWindowFocused();
|
||||
const auto shellSnapshot = m_xcuiInputSource.CaptureSnapshot(options);
|
||||
const auto shellFrameDelta = DispatchShellShortcuts(shellSnapshot);
|
||||
::XCEngine::UI::UIDrawData nativeShellDrawData = BuildNativeShellDrawData(shellSnapshot, shellFrameDelta);
|
||||
nativeCompositor->SubmitRenderPacket(nativeShellDrawData, &m_hostedPreviewTextAtlasProvider);
|
||||
|
||||
m_windowCompositor->RenderFrame(
|
||||
kClearColor,
|
||||
{},
|
||||
[this](
|
||||
const ::XCEngine::Rendering::RenderContext& renderContext,
|
||||
const ::XCEngine::Rendering::RenderSurface& surface) {
|
||||
MainWindowNativeBackdropRenderer::FrameState frameState = {};
|
||||
frameState.elapsedSeconds = static_cast<float>(
|
||||
std::chrono::duration<double>(std::chrono::steady_clock::now() - m_startTime).count());
|
||||
frameState.pulseAccent = IsShellViewToggleEnabled(ShellViewToggleId::PulseAccent);
|
||||
frameState.drawBackdrop = IsShellViewToggleEnabled(ShellViewToggleId::NativeBackdrop);
|
||||
m_nativeBackdropRenderer.Render(renderContext, surface, frameState);
|
||||
});
|
||||
|
||||
m_xcuiInputSource.ClearFrameTransients();
|
||||
}
|
||||
|
||||
void Application::RenderShellChrome() {
|
||||
SyncShellChromePanelStateFromPanels();
|
||||
|
||||
@@ -1023,75 +1674,17 @@ void Application::Frame() {
|
||||
return;
|
||||
}
|
||||
|
||||
m_hostedPreviewQueue.BeginFrame();
|
||||
m_hostedPreviewSurfaceRegistry.BeginFrame();
|
||||
SyncHostedPreviewSurfaces();
|
||||
if (m_windowCompositor == nullptr) {
|
||||
m_xcuiInputSource.ClearFrameTransients();
|
||||
return;
|
||||
}
|
||||
|
||||
m_windowCompositor->RenderFrame(
|
||||
kClearColor,
|
||||
[this]() {
|
||||
DispatchShellShortcuts();
|
||||
RenderShellChrome();
|
||||
if (m_demoPanel) {
|
||||
m_demoPanel->RenderIfVisible();
|
||||
}
|
||||
if (m_layoutLabPanel) {
|
||||
m_layoutLabPanel->RenderIfVisible();
|
||||
}
|
||||
bool showImGuiDemoWindow = IsShellViewToggleEnabled(ShellViewToggleId::ImGuiDemoWindow);
|
||||
if (showImGuiDemoWindow) {
|
||||
ImGui::ShowDemoWindow(&showImGuiDemoWindow);
|
||||
SetShellViewToggleEnabled(ShellViewToggleId::ImGuiDemoWindow, showImGuiDemoWindow);
|
||||
}
|
||||
if (IsNativeWindowHostEnabled()) {
|
||||
FrameNativeXCUIHost();
|
||||
return;
|
||||
}
|
||||
|
||||
SyncShellChromePanelStateFromPanels();
|
||||
SyncHostedPreviewSurfaces();
|
||||
},
|
||||
[this](
|
||||
const ::XCEngine::Rendering::RenderContext& renderContext,
|
||||
const ::XCEngine::Rendering::RenderSurface& surface) {
|
||||
RenderQueuedHostedPreviews(renderContext, surface);
|
||||
|
||||
if (!IsShellViewToggleEnabled(ShellViewToggleId::NativeBackdrop) &&
|
||||
!IsShellViewToggleEnabled(ShellViewToggleId::NativeXCUIOverlay)) {
|
||||
return;
|
||||
}
|
||||
|
||||
MainWindowNativeBackdropRenderer::FrameState frameState = {};
|
||||
frameState.elapsedSeconds = static_cast<float>(
|
||||
std::chrono::duration<double>(std::chrono::steady_clock::now() - m_startTime).count());
|
||||
frameState.pulseAccent = IsShellViewToggleEnabled(ShellViewToggleId::PulseAccent);
|
||||
frameState.drawBackdrop = IsShellViewToggleEnabled(ShellViewToggleId::NativeBackdrop);
|
||||
if (IsShellViewToggleEnabled(ShellViewToggleId::NativeXCUIOverlay)) {
|
||||
const float width = static_cast<float>(surface.GetWidth());
|
||||
const float height = static_cast<float>(surface.GetHeight());
|
||||
const float horizontalMargin = (std::min)(width * 0.14f, 128.0f);
|
||||
const float topMargin = (std::min)(height * 0.15f, 132.0f);
|
||||
const float bottomMargin = (std::min)(height * 0.12f, 96.0f);
|
||||
|
||||
::XCEngine::Editor::XCUIBackend::XCUILayoutLabInputState overlayInput = {};
|
||||
overlayInput.canvasRect = ::XCEngine::UI::UIRect(
|
||||
horizontalMargin,
|
||||
topMargin,
|
||||
(std::max)(0.0f, width - horizontalMargin * 2.0f),
|
||||
(std::max)(0.0f, height - topMargin - bottomMargin));
|
||||
overlayInput.pointerPosition = m_xcuiInputSource.GetPointerPosition();
|
||||
overlayInput.pointerInside =
|
||||
overlayInput.pointerPosition.x >= overlayInput.canvasRect.x &&
|
||||
overlayInput.pointerPosition.y >= overlayInput.canvasRect.y &&
|
||||
overlayInput.pointerPosition.x <= overlayInput.canvasRect.x + overlayInput.canvasRect.width &&
|
||||
overlayInput.pointerPosition.y <= overlayInput.canvasRect.y + overlayInput.canvasRect.height;
|
||||
const auto& overlayFrame = m_nativeOverlayRuntime.Update(overlayInput);
|
||||
frameState.overlayDrawData = &overlayFrame.drawData;
|
||||
}
|
||||
m_nativeBackdropRenderer.Render(renderContext, surface, frameState);
|
||||
});
|
||||
|
||||
m_xcuiInputSource.ClearFrameTransients();
|
||||
FrameLegacyImGuiHost();
|
||||
}
|
||||
|
||||
} // namespace NewEditor
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
#include "Platform/D3D12WindowRenderer.h"
|
||||
#include "Rendering/MainWindowNativeBackdropRenderer.h"
|
||||
#include "XCUIBackend/IWindowUICompositor.h"
|
||||
#include "XCUIBackend/NativeXCUIPanelCanvasHost.h"
|
||||
#include "XCUIBackend/XCUIDemoRuntime.h"
|
||||
#include "XCUIBackend/XCUIHostedPreviewPresenter.h"
|
||||
#include "XCUIBackend/XCUIInputBridge.h"
|
||||
#include "XCUIBackend/XCUILayoutLabRuntime.h"
|
||||
@@ -36,6 +38,10 @@ public:
|
||||
using ShellHostedPreviewMode = ::XCEngine::Editor::XCUIBackend::XCUIShellHostedPreviewMode;
|
||||
using ShellPanelChromeState = ::XCEngine::Editor::XCUIBackend::XCUIShellPanelChromeState;
|
||||
using ShellViewToggleState = ::XCEngine::Editor::XCUIBackend::XCUIShellViewToggleState;
|
||||
enum class WindowHostMode : std::uint8_t {
|
||||
NativeXCUI = 0,
|
||||
LegacyImGui
|
||||
};
|
||||
|
||||
struct ShellCommandIds {
|
||||
static constexpr const char* ToggleXCUIDemoPanel =
|
||||
@@ -297,6 +303,7 @@ private:
|
||||
bool CreateMainWindow(HINSTANCE instance, int nCmdShow);
|
||||
bool InitializeRenderer();
|
||||
void InitializeWindowCompositor();
|
||||
void InitializeNativeShell();
|
||||
void ShutdownWindowCompositor();
|
||||
void ShutdownRenderer();
|
||||
void DestroyHostedPreviewSurfaces();
|
||||
@@ -329,7 +336,14 @@ private:
|
||||
const ::XCEngine::Rendering::RenderContext& renderContext,
|
||||
const ::XCEngine::UI::UIDrawData& drawData);
|
||||
void ConfigureShellCommandRouter();
|
||||
void DispatchShellShortcuts();
|
||||
::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameDelta DispatchShellShortcuts(
|
||||
const ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot& snapshot);
|
||||
bool IsNativeWindowHostEnabled() const;
|
||||
::XCEngine::UI::UIDrawData BuildNativeShellDrawData(
|
||||
const ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot& shellSnapshot,
|
||||
const ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameDelta& shellFrameDelta);
|
||||
void FrameLegacyImGuiHost();
|
||||
void FrameNativeXCUIHost();
|
||||
void RenderShellChrome();
|
||||
void RenderHostedPreviewHud();
|
||||
void RenderQueuedHostedPreviews(
|
||||
@@ -351,6 +365,16 @@ private:
|
||||
::XCEngine::Editor::XCUIBackend::XCUIRHIRenderBackend m_hostedPreviewRenderBackend;
|
||||
ShellChromeState m_shellChromeState = {};
|
||||
std::vector<HostedPreviewOffscreenSurface> m_hostedPreviewSurfaces = {};
|
||||
WindowHostMode m_windowHostMode = WindowHostMode::NativeXCUI;
|
||||
::XCEngine::Editor::XCUIBackend::XCUIDemoRuntime m_nativeDemoRuntime;
|
||||
::XCEngine::Editor::XCUIBackend::XCUILayoutLabRuntime m_nativeLayoutRuntime;
|
||||
::XCEngine::Editor::XCUIBackend::XCUIInputBridge m_nativeDemoInputBridge;
|
||||
::XCEngine::Editor::XCUIBackend::XCUIInputBridge m_nativeLayoutInputBridge;
|
||||
::XCEngine::Editor::XCUIBackend::NativeXCUIPanelCanvasHost m_nativeDemoCanvasHost;
|
||||
::XCEngine::Editor::XCUIBackend::NativeXCUIPanelCanvasHost m_nativeLayoutCanvasHost;
|
||||
ShellPanelId m_nativeActivePanel = ShellPanelId::XCUIDemo;
|
||||
bool m_nativeDemoReloadSucceeded = false;
|
||||
bool m_nativeLayoutReloadSucceeded = false;
|
||||
::XCEngine::Editor::XCUIBackend::XCUILayoutLabRuntime m_nativeOverlayRuntime;
|
||||
MainWindowNativeBackdropRenderer m_nativeBackdropRenderer;
|
||||
bool m_running = false;
|
||||
|
||||
218
new_editor/src/XCUIBackend/NativeWindowUICompositor.cpp
Normal file
218
new_editor/src/XCUIBackend/NativeWindowUICompositor.cpp
Normal file
@@ -0,0 +1,218 @@
|
||||
#include "XCUIBackend/NativeWindowUICompositor.h"
|
||||
|
||||
#include <XCEngine/Rendering/RenderContext.h>
|
||||
#include <XCEngine/Rendering/RenderSurface.h>
|
||||
#include <XCEngine/RHI/RHICommandList.h>
|
||||
#include <XCEngine/RHI/RHICommandQueue.h>
|
||||
#include <XCEngine/RHI/RHIResourceView.h>
|
||||
#include <XCEngine/RHI/RHISwapChain.h>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace XCUIBackend {
|
||||
|
||||
namespace {
|
||||
|
||||
bool PrepareSwapChainRender(
|
||||
::XCEngine::Editor::Platform::D3D12WindowRenderer& windowRenderer,
|
||||
const float clearColor[4],
|
||||
::XCEngine::Rendering::RenderContext& outRenderContext,
|
||||
const ::XCEngine::Rendering::RenderSurface*& outRenderSurface,
|
||||
::XCEngine::RHI::RHIResourceView*& outRenderTargetView) {
|
||||
outRenderContext = windowRenderer.GetRenderContext();
|
||||
outRenderSurface = windowRenderer.GetCurrentRenderSurface();
|
||||
outRenderTargetView = nullptr;
|
||||
|
||||
if (!outRenderContext.IsValid() ||
|
||||
outRenderSurface == nullptr ||
|
||||
outRenderContext.commandList == nullptr ||
|
||||
windowRenderer.GetSwapChain() == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& colorAttachments = outRenderSurface->GetColorAttachments();
|
||||
if (colorAttachments.empty() || colorAttachments[0] == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outRenderTargetView = colorAttachments[0];
|
||||
outRenderContext.commandList->TransitionBarrier(
|
||||
outRenderTargetView,
|
||||
::XCEngine::RHI::ResourceStates::Present,
|
||||
::XCEngine::RHI::ResourceStates::RenderTarget);
|
||||
outRenderContext.commandList->SetRenderTargets(1, &outRenderTargetView, nullptr);
|
||||
outRenderContext.commandList->ClearRenderTarget(outRenderTargetView, clearColor);
|
||||
return true;
|
||||
}
|
||||
|
||||
void RebindSwapChainRenderTarget(
|
||||
::XCEngine::Rendering::RenderContext& renderContext,
|
||||
::XCEngine::RHI::RHIResourceView* renderTargetView) {
|
||||
if (renderContext.commandList == nullptr || renderTargetView == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
renderContext.commandList->SetRenderTargets(1, &renderTargetView, nullptr);
|
||||
}
|
||||
|
||||
void PresentSwapChainRender(
|
||||
::XCEngine::Editor::Platform::D3D12WindowRenderer& windowRenderer,
|
||||
::XCEngine::Rendering::RenderContext& renderContext,
|
||||
::XCEngine::RHI::RHIResourceView* renderTargetView) {
|
||||
if (renderContext.commandList == nullptr ||
|
||||
renderContext.commandQueue == nullptr ||
|
||||
renderTargetView == nullptr ||
|
||||
windowRenderer.GetSwapChain() == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
renderContext.commandList->TransitionBarrier(
|
||||
renderTargetView,
|
||||
::XCEngine::RHI::ResourceStates::RenderTarget,
|
||||
::XCEngine::RHI::ResourceStates::Present);
|
||||
renderContext.commandList->Close();
|
||||
|
||||
void* commandLists[] = { renderContext.commandList };
|
||||
renderContext.commandQueue->ExecuteCommandLists(1, commandLists);
|
||||
windowRenderer.GetSwapChain()->Present(1, 0);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool NativeWindowUICompositor::Initialize(
|
||||
HWND hwnd,
|
||||
::XCEngine::Editor::Platform::D3D12WindowRenderer& windowRenderer,
|
||||
const ConfigureFontsCallback& configureFonts) {
|
||||
(void)configureFonts;
|
||||
|
||||
m_hwnd = hwnd;
|
||||
m_windowRenderer = hwnd != nullptr ? &windowRenderer : nullptr;
|
||||
m_renderBackend.Shutdown();
|
||||
m_renderBackend.ResetStats();
|
||||
m_pendingRenderPacket.Clear();
|
||||
m_lastPresentStats = {};
|
||||
return m_windowRenderer != nullptr;
|
||||
}
|
||||
|
||||
void NativeWindowUICompositor::Shutdown() {
|
||||
m_renderBackend.Shutdown();
|
||||
m_pendingRenderPacket.Clear();
|
||||
m_lastPresentStats = {};
|
||||
m_windowRenderer = nullptr;
|
||||
m_hwnd = nullptr;
|
||||
}
|
||||
|
||||
bool NativeWindowUICompositor::HandleWindowMessage(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
|
||||
(void)hwnd;
|
||||
(void)message;
|
||||
(void)wParam;
|
||||
(void)lParam;
|
||||
return false;
|
||||
}
|
||||
|
||||
void NativeWindowUICompositor::RenderFrame(
|
||||
const float clearColor[4],
|
||||
const UiRenderCallback& renderUi,
|
||||
const RenderCallback& beforeUiRender,
|
||||
const RenderCallback& afterUiRender) {
|
||||
(void)renderUi;
|
||||
|
||||
m_lastPresentStats = {};
|
||||
if (m_windowRenderer == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
::XCEngine::Rendering::RenderContext renderContext = {};
|
||||
const ::XCEngine::Rendering::RenderSurface* renderSurface = nullptr;
|
||||
::XCEngine::RHI::RHIResourceView* renderTargetView = nullptr;
|
||||
if (!PrepareSwapChainRender(
|
||||
*m_windowRenderer,
|
||||
clearColor,
|
||||
renderContext,
|
||||
renderSurface,
|
||||
renderTargetView)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (beforeUiRender) {
|
||||
beforeUiRender(renderContext, *renderSurface);
|
||||
RebindSwapChainRenderTarget(renderContext, renderTargetView);
|
||||
}
|
||||
|
||||
m_lastPresentStats.hadPendingPacket = m_pendingRenderPacket.HasDrawData();
|
||||
m_lastPresentStats.submittedDrawListCount = m_pendingRenderPacket.drawData.GetDrawListCount();
|
||||
m_lastPresentStats.submittedCommandCount = m_pendingRenderPacket.drawData.GetTotalCommandCount();
|
||||
|
||||
if (m_pendingRenderPacket.textAtlasProvider != nullptr) {
|
||||
m_renderBackend.SetTextAtlasProvider(m_pendingRenderPacket.textAtlasProvider);
|
||||
} else {
|
||||
m_renderBackend.SetTextAtlasProvider(nullptr);
|
||||
}
|
||||
|
||||
if (m_lastPresentStats.hadPendingPacket) {
|
||||
m_lastPresentStats.renderedNativeOverlay =
|
||||
m_renderBackend.Render(renderContext, *renderSurface, m_pendingRenderPacket.drawData);
|
||||
m_lastPresentStats.overlayStats = m_renderBackend.GetLastOverlayStats();
|
||||
} else {
|
||||
m_renderBackend.ResetStats();
|
||||
m_lastPresentStats.overlayStats = m_renderBackend.GetLastOverlayStats();
|
||||
}
|
||||
|
||||
m_pendingRenderPacket.Clear();
|
||||
|
||||
if (afterUiRender) {
|
||||
RebindSwapChainRenderTarget(renderContext, renderTargetView);
|
||||
afterUiRender(renderContext, *renderSurface);
|
||||
}
|
||||
|
||||
PresentSwapChainRender(*m_windowRenderer, renderContext, renderTargetView);
|
||||
}
|
||||
|
||||
bool NativeWindowUICompositor::CreateTextureDescriptor(
|
||||
::XCEngine::RHI::RHIDevice* device,
|
||||
::XCEngine::RHI::RHITexture* texture,
|
||||
UITextureRegistration& outRegistration) {
|
||||
(void)device;
|
||||
(void)texture;
|
||||
outRegistration = {};
|
||||
return false;
|
||||
}
|
||||
|
||||
void NativeWindowUICompositor::FreeTextureDescriptor(const UITextureRegistration& registration) {
|
||||
(void)registration;
|
||||
}
|
||||
|
||||
void NativeWindowUICompositor::SubmitRenderPacket(const XCUINativeWindowRenderPacket& packet) {
|
||||
m_pendingRenderPacket = packet;
|
||||
}
|
||||
|
||||
void NativeWindowUICompositor::SubmitRenderPacket(
|
||||
const ::XCEngine::UI::UIDrawData& drawData,
|
||||
const IXCUITextAtlasProvider* textAtlasProvider) {
|
||||
m_pendingRenderPacket.drawData = drawData;
|
||||
m_pendingRenderPacket.textAtlasProvider = textAtlasProvider;
|
||||
}
|
||||
|
||||
void NativeWindowUICompositor::ClearPendingRenderPacket() {
|
||||
m_pendingRenderPacket.Clear();
|
||||
}
|
||||
|
||||
bool NativeWindowUICompositor::HasPendingRenderPacket() const {
|
||||
return m_pendingRenderPacket.HasDrawData();
|
||||
}
|
||||
|
||||
const XCUINativeWindowRenderPacket& NativeWindowUICompositor::GetPendingRenderPacket() const {
|
||||
return m_pendingRenderPacket;
|
||||
}
|
||||
|
||||
const XCUINativeWindowPresentStats& NativeWindowUICompositor::GetLastPresentStats() const {
|
||||
return m_lastPresentStats;
|
||||
}
|
||||
|
||||
std::unique_ptr<IWindowUICompositor> CreateNativeWindowUICompositor() {
|
||||
return std::make_unique<NativeWindowUICompositor>();
|
||||
}
|
||||
|
||||
} // namespace XCUIBackend
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
76
new_editor/src/XCUIBackend/NativeWindowUICompositor.h
Normal file
76
new_editor/src/XCUIBackend/NativeWindowUICompositor.h
Normal file
@@ -0,0 +1,76 @@
|
||||
#pragma once
|
||||
|
||||
#include "XCUIBackend/IWindowUICompositor.h"
|
||||
#include "XCUIBackend/IXCUITextAtlasProvider.h"
|
||||
#include "XCUIBackend/XCUIRHIRenderBackend.h"
|
||||
|
||||
#include <XCEngine/UI/DrawData.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace XCUIBackend {
|
||||
|
||||
struct XCUINativeWindowRenderPacket {
|
||||
::XCEngine::UI::UIDrawData drawData = {};
|
||||
const IXCUITextAtlasProvider* textAtlasProvider = nullptr;
|
||||
|
||||
void Clear() {
|
||||
drawData.Clear();
|
||||
textAtlasProvider = nullptr;
|
||||
}
|
||||
|
||||
bool HasDrawData() const {
|
||||
return drawData.GetDrawListCount() > 0u && drawData.GetTotalCommandCount() > 0u;
|
||||
}
|
||||
};
|
||||
|
||||
struct XCUINativeWindowPresentStats {
|
||||
bool hadPendingPacket = false;
|
||||
bool renderedNativeOverlay = false;
|
||||
std::size_t submittedDrawListCount = 0;
|
||||
std::size_t submittedCommandCount = 0;
|
||||
XCUIRHIRenderBackend::OverlayStats overlayStats = {};
|
||||
};
|
||||
|
||||
class NativeWindowUICompositor final : public IWindowUICompositor {
|
||||
public:
|
||||
bool Initialize(
|
||||
HWND hwnd,
|
||||
::XCEngine::Editor::Platform::D3D12WindowRenderer& windowRenderer,
|
||||
const ConfigureFontsCallback& configureFonts) override;
|
||||
void Shutdown() override;
|
||||
bool HandleWindowMessage(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) override;
|
||||
void RenderFrame(
|
||||
const float clearColor[4],
|
||||
const UiRenderCallback& renderUi,
|
||||
const RenderCallback& beforeUiRender = {},
|
||||
const RenderCallback& afterUiRender = {}) override;
|
||||
bool CreateTextureDescriptor(
|
||||
::XCEngine::RHI::RHIDevice* device,
|
||||
::XCEngine::RHI::RHITexture* texture,
|
||||
UITextureRegistration& outRegistration) override;
|
||||
void FreeTextureDescriptor(const UITextureRegistration& registration) override;
|
||||
|
||||
void SubmitRenderPacket(const XCUINativeWindowRenderPacket& packet);
|
||||
void SubmitRenderPacket(
|
||||
const ::XCEngine::UI::UIDrawData& drawData,
|
||||
const IXCUITextAtlasProvider* textAtlasProvider = nullptr);
|
||||
void ClearPendingRenderPacket();
|
||||
bool HasPendingRenderPacket() const;
|
||||
const XCUINativeWindowRenderPacket& GetPendingRenderPacket() const;
|
||||
const XCUINativeWindowPresentStats& GetLastPresentStats() const;
|
||||
|
||||
private:
|
||||
HWND m_hwnd = nullptr;
|
||||
::XCEngine::Editor::Platform::D3D12WindowRenderer* m_windowRenderer = nullptr;
|
||||
XCUIRHIRenderBackend m_renderBackend = {};
|
||||
XCUINativeWindowRenderPacket m_pendingRenderPacket = {};
|
||||
XCUINativeWindowPresentStats m_lastPresentStats = {};
|
||||
};
|
||||
|
||||
} // namespace XCUIBackend
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
294
new_editor/src/XCUIBackend/NativeXCUIPanelCanvasHost.h
Normal file
294
new_editor/src/XCUIBackend/NativeXCUIPanelCanvasHost.h
Normal file
@@ -0,0 +1,294 @@
|
||||
#pragma once
|
||||
|
||||
#include "XCUIBackend/XCUIPanelCanvasHost.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace XCUIBackend {
|
||||
|
||||
namespace detail {
|
||||
|
||||
inline std::string CopyCanvasLabel(const char* text) {
|
||||
return text != nullptr ? std::string(text) : std::string();
|
||||
}
|
||||
|
||||
inline ::XCEngine::UI::UIColor NativeCanvasPlaceholderFillColor() {
|
||||
return ::XCEngine::UI::UIColor(18.0f / 255.0f, 24.0f / 255.0f, 32.0f / 255.0f, 1.0f);
|
||||
}
|
||||
|
||||
inline ::XCEngine::UI::UIColor NativeCanvasPlaceholderStrokeColor() {
|
||||
return ::XCEngine::UI::UIColor(54.0f / 255.0f, 72.0f / 255.0f, 94.0f / 255.0f, 1.0f);
|
||||
}
|
||||
|
||||
inline ::XCEngine::UI::UIColor NativeCanvasPrimaryTextColor() {
|
||||
return ::XCEngine::UI::UIColor(191.0f / 255.0f, 205.0f / 255.0f, 224.0f / 255.0f, 1.0f);
|
||||
}
|
||||
|
||||
inline ::XCEngine::UI::UIColor NativeCanvasSecondaryTextColor() {
|
||||
return ::XCEngine::UI::UIColor(132.0f / 255.0f, 147.0f / 255.0f, 170.0f / 255.0f, 1.0f);
|
||||
}
|
||||
|
||||
inline XCUIPanelCanvasSession NormalizeNativeCanvasSession(
|
||||
const XCUIPanelCanvasRequest& request,
|
||||
const XCUIPanelCanvasSession* configuredSession) {
|
||||
if (configuredSession == nullptr) {
|
||||
return BuildPassiveXCUIPanelCanvasSession(request);
|
||||
}
|
||||
|
||||
XCUIPanelCanvasSession session = *configuredSession;
|
||||
const float fallbackHostHeight = request.height > 0.0f ? request.height : 0.0f;
|
||||
if (session.hostRect.height <= 0.0f && fallbackHostHeight > 0.0f) {
|
||||
session.hostRect.height = fallbackHostHeight;
|
||||
}
|
||||
|
||||
if (session.canvasRect.width <= 0.0f || session.canvasRect.height <= 0.0f) {
|
||||
const float topInset = request.topInset > 0.0f ? request.topInset : 0.0f;
|
||||
const float clampedTopInset =
|
||||
session.hostRect.height > 0.0f
|
||||
? (std::min)(topInset, session.hostRect.height)
|
||||
: 0.0f;
|
||||
session.canvasRect = ::XCEngine::UI::UIRect(
|
||||
session.hostRect.x,
|
||||
session.hostRect.y + clampedTopInset,
|
||||
session.hostRect.width,
|
||||
(std::max)(0.0f, session.hostRect.height - clampedTopInset));
|
||||
}
|
||||
|
||||
session.validCanvas =
|
||||
session.canvasRect.width > 1.0f &&
|
||||
session.canvasRect.height > 1.0f;
|
||||
if (!session.validCanvas) {
|
||||
session.hovered = false;
|
||||
}
|
||||
return session;
|
||||
}
|
||||
|
||||
inline void RecordPlaceholder(
|
||||
::XCEngine::UI::UIDrawList& drawList,
|
||||
const ::XCEngine::UI::UIRect& rect,
|
||||
const std::string& title,
|
||||
const std::string& subtitle) {
|
||||
if (rect.width <= 1.0f || rect.height <= 1.0f) {
|
||||
return;
|
||||
}
|
||||
|
||||
drawList.AddFilledRect(rect, NativeCanvasPlaceholderFillColor(), 8.0f);
|
||||
drawList.AddRectOutline(rect, NativeCanvasPlaceholderStrokeColor(), 1.0f, 8.0f);
|
||||
if (!title.empty()) {
|
||||
drawList.AddText(
|
||||
::XCEngine::UI::UIPoint(rect.x + 14.0f, rect.y + 14.0f),
|
||||
title,
|
||||
NativeCanvasPrimaryTextColor());
|
||||
}
|
||||
if (!subtitle.empty()) {
|
||||
drawList.AddText(
|
||||
::XCEngine::UI::UIPoint(rect.x + 14.0f, rect.y + 36.0f),
|
||||
subtitle,
|
||||
NativeCanvasSecondaryTextColor());
|
||||
}
|
||||
}
|
||||
|
||||
inline void RecordBadge(
|
||||
::XCEngine::UI::UIDrawList& drawList,
|
||||
const ::XCEngine::UI::UIRect& canvasRect,
|
||||
const std::string& title,
|
||||
const std::string& subtitle) {
|
||||
if (title.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ::XCEngine::UI::UIRect badgeRect(
|
||||
canvasRect.x + 10.0f,
|
||||
canvasRect.y + 10.0f,
|
||||
290.0f,
|
||||
42.0f);
|
||||
drawList.AddFilledRect(
|
||||
badgeRect,
|
||||
::XCEngine::UI::UIColor(16.0f / 255.0f, 22.0f / 255.0f, 30.0f / 255.0f, 216.0f / 255.0f),
|
||||
8.0f);
|
||||
drawList.AddRectOutline(badgeRect, NativeCanvasPlaceholderStrokeColor(), 1.0f, 8.0f);
|
||||
drawList.AddText(
|
||||
::XCEngine::UI::UIPoint(badgeRect.x + 10.0f, badgeRect.y + 8.0f),
|
||||
title,
|
||||
NativeCanvasPrimaryTextColor());
|
||||
if (!subtitle.empty()) {
|
||||
drawList.AddText(
|
||||
::XCEngine::UI::UIPoint(badgeRect.x + 10.0f, badgeRect.y + 24.0f),
|
||||
subtitle,
|
||||
NativeCanvasSecondaryTextColor());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
class NativeXCUIPanelCanvasHost final : public IXCUIPanelCanvasHost {
|
||||
public:
|
||||
const char* GetDebugName() const override {
|
||||
return "NativeXCUIPanelCanvasHost";
|
||||
}
|
||||
|
||||
XCUIPanelCanvasHostBackend GetBackend() const override {
|
||||
return XCUIPanelCanvasHostBackend::Native;
|
||||
}
|
||||
|
||||
XCUIPanelCanvasHostCapabilities GetCapabilities() const override {
|
||||
XCUIPanelCanvasHostCapabilities capabilities = {};
|
||||
capabilities.supportsHostedSurfaceImages = true;
|
||||
capabilities.supportsPrimitiveOverlays = true;
|
||||
capabilities.supportsExternallyDrivenSession = true;
|
||||
return capabilities;
|
||||
}
|
||||
|
||||
void SetCanvasSession(const XCUIPanelCanvasSession& session) {
|
||||
m_configuredSession = session;
|
||||
m_hasConfiguredSession = true;
|
||||
}
|
||||
|
||||
void ClearCanvasSession() {
|
||||
m_configuredSession = {};
|
||||
m_hasConfiguredSession = false;
|
||||
}
|
||||
|
||||
bool HasConfiguredSession() const {
|
||||
return m_hasConfiguredSession;
|
||||
}
|
||||
|
||||
const XCUIPanelCanvasSession& GetConfiguredSession() const {
|
||||
return m_configuredSession;
|
||||
}
|
||||
|
||||
XCUIPanelCanvasSession BeginCanvas(const XCUIPanelCanvasRequest& request) override {
|
||||
m_currentFrame = {};
|
||||
m_currentFrame.childId = ResolveXCUIPanelCanvasChildId(request, "NativeXCUIPanelCanvasHost");
|
||||
m_currentFrame.session = detail::NormalizeNativeCanvasSession(
|
||||
request,
|
||||
m_hasConfiguredSession ? &m_configuredSession : nullptr);
|
||||
m_currentFrame.bordered = request.bordered;
|
||||
m_currentFrame.drawPreviewFrame = request.drawPreviewFrame;
|
||||
m_currentFrame.showingSurfaceImage = request.showSurfaceImage && request.surfaceImage.IsValid();
|
||||
m_currentFrame.placeholderTitle = detail::CopyCanvasLabel(request.placeholderTitle);
|
||||
m_currentFrame.placeholderSubtitle = detail::CopyCanvasLabel(request.placeholderSubtitle);
|
||||
m_currentFrame.badgeTitle = detail::CopyCanvasLabel(request.badgeTitle);
|
||||
m_currentFrame.badgeSubtitle = detail::CopyCanvasLabel(request.badgeSubtitle);
|
||||
m_currentFrame.surfaceImage = request.surfaceImage;
|
||||
|
||||
m_overlayDrawList = nullptr;
|
||||
m_clipDepth = 0u;
|
||||
|
||||
if (m_currentFrame.session.validCanvas) {
|
||||
const bool shouldRecordPlaceholder =
|
||||
!m_currentFrame.showingSurfaceImage &&
|
||||
(!m_currentFrame.placeholderTitle.empty() || !m_currentFrame.placeholderSubtitle.empty());
|
||||
if (shouldRecordPlaceholder) {
|
||||
::XCEngine::UI::UIDrawList& drawList = EnsureOverlayDrawList();
|
||||
detail::RecordPlaceholder(
|
||||
drawList,
|
||||
m_currentFrame.session.canvasRect,
|
||||
m_currentFrame.placeholderTitle,
|
||||
m_currentFrame.placeholderSubtitle);
|
||||
}
|
||||
|
||||
if (request.drawPreviewFrame) {
|
||||
DrawOutlineRect(
|
||||
m_currentFrame.session.canvasRect,
|
||||
detail::NativeCanvasPlaceholderStrokeColor(),
|
||||
1.0f,
|
||||
8.0f);
|
||||
}
|
||||
|
||||
if (!m_currentFrame.badgeTitle.empty()) {
|
||||
::XCEngine::UI::UIDrawList& drawList = EnsureOverlayDrawList();
|
||||
detail::RecordBadge(
|
||||
drawList,
|
||||
m_currentFrame.session.canvasRect,
|
||||
m_currentFrame.badgeTitle,
|
||||
m_currentFrame.badgeSubtitle);
|
||||
}
|
||||
}
|
||||
|
||||
return m_currentFrame.session;
|
||||
}
|
||||
|
||||
void DrawFilledRect(
|
||||
const ::XCEngine::UI::UIRect& rect,
|
||||
const ::XCEngine::UI::UIColor& color,
|
||||
float rounding = 0.0f) override {
|
||||
if (rect.width <= 0.0f || rect.height <= 0.0f) {
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureOverlayDrawList().AddFilledRect(rect, color, rounding);
|
||||
}
|
||||
|
||||
void DrawOutlineRect(
|
||||
const ::XCEngine::UI::UIRect& rect,
|
||||
const ::XCEngine::UI::UIColor& color,
|
||||
float thickness = 1.0f,
|
||||
float rounding = 0.0f) override {
|
||||
if (rect.width <= 0.0f || rect.height <= 0.0f) {
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureOverlayDrawList().AddRectOutline(rect, color, thickness, rounding);
|
||||
}
|
||||
|
||||
void DrawText(
|
||||
const ::XCEngine::UI::UIPoint& position,
|
||||
std::string_view text,
|
||||
const ::XCEngine::UI::UIColor& color,
|
||||
float fontSize = 0.0f) override {
|
||||
if (text.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureOverlayDrawList().AddText(position, std::string(text), color, fontSize);
|
||||
}
|
||||
|
||||
void EndCanvas() override {
|
||||
if (m_overlayDrawList != nullptr) {
|
||||
while (m_clipDepth > 0u) {
|
||||
m_overlayDrawList->PopClipRect();
|
||||
--m_clipDepth;
|
||||
}
|
||||
}
|
||||
|
||||
m_overlayDrawList = nullptr;
|
||||
}
|
||||
|
||||
bool TryGetLatestFrameSnapshot(XCUIPanelCanvasFrameSnapshot& outSnapshot) const override {
|
||||
outSnapshot = m_currentFrame;
|
||||
return !outSnapshot.childId.empty();
|
||||
}
|
||||
|
||||
private:
|
||||
::XCEngine::UI::UIDrawList& EnsureOverlayDrawList() {
|
||||
if (m_overlayDrawList == nullptr) {
|
||||
::XCEngine::UI::UIDrawList& drawList =
|
||||
m_currentFrame.overlayDrawData.EmplaceDrawList(m_currentFrame.childId + ".overlay");
|
||||
drawList.PushClipRect(m_currentFrame.session.canvasRect, true);
|
||||
m_overlayDrawList = &drawList;
|
||||
m_clipDepth = 1u;
|
||||
}
|
||||
|
||||
return *m_overlayDrawList;
|
||||
}
|
||||
|
||||
bool m_hasConfiguredSession = false;
|
||||
XCUIPanelCanvasSession m_configuredSession = {};
|
||||
XCUIPanelCanvasFrameSnapshot m_currentFrame = {};
|
||||
::XCEngine::UI::UIDrawList* m_overlayDrawList = nullptr;
|
||||
std::size_t m_clipDepth = 0u;
|
||||
};
|
||||
|
||||
inline std::unique_ptr<IXCUIPanelCanvasHost> CreateNativeXCUIPanelCanvasHost() {
|
||||
return std::make_unique<NativeXCUIPanelCanvasHost>();
|
||||
}
|
||||
|
||||
} // namespace XCUIBackend
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
@@ -64,13 +62,6 @@ void AppendUniqueKeyCode(std::vector<std::int32_t>& keyCodes, std::int32_t keyCo
|
||||
}
|
||||
}
|
||||
|
||||
bool IsPointerPositionValid(const ImVec2& position) {
|
||||
return std::isfinite(position.x) &&
|
||||
std::isfinite(position.y) &&
|
||||
position.x > -std::numeric_limits<float>::max() * 0.5f &&
|
||||
position.y > -std::numeric_limits<float>::max() * 0.5f;
|
||||
}
|
||||
|
||||
std::uint64_t GetTimestampNanoseconds() {
|
||||
return static_cast<std::uint64_t>(
|
||||
std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
#include <XCEngine/Input/InputTypes.h>
|
||||
#include <XCEngine/UI/Types.h>
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
@@ -136,6 +136,12 @@ set(NEW_EDITOR_WINDOW_UI_COMPOSITOR_HEADER
|
||||
set(NEW_EDITOR_IMGUI_WINDOW_UI_COMPOSITOR_HEADER
|
||||
${CMAKE_SOURCE_DIR}/new_editor/src/XCUIBackend/ImGuiWindowUICompositor.h
|
||||
)
|
||||
set(NEW_EDITOR_NATIVE_WINDOW_UI_COMPOSITOR_HEADER
|
||||
${CMAKE_SOURCE_DIR}/new_editor/src/XCUIBackend/NativeWindowUICompositor.h
|
||||
)
|
||||
set(NEW_EDITOR_NATIVE_WINDOW_UI_COMPOSITOR_SOURCE
|
||||
${CMAKE_SOURCE_DIR}/new_editor/src/XCUIBackend/NativeWindowUICompositor.cpp
|
||||
)
|
||||
set(NEW_EDITOR_NATIVE_BACKDROP_RENDERER_HEADER
|
||||
${CMAKE_SOURCE_DIR}/new_editor/src/Rendering/MainWindowNativeBackdropRenderer.h
|
||||
)
|
||||
@@ -436,6 +442,43 @@ else()
|
||||
message(STATUS "Skipping new_editor_imgui_window_ui_compositor_tests because compositor headers or the test source are missing.")
|
||||
endif()
|
||||
|
||||
if(EXISTS "${NEW_EDITOR_WINDOW_UI_COMPOSITOR_HEADER}" AND
|
||||
EXISTS "${NEW_EDITOR_NATIVE_WINDOW_UI_COMPOSITOR_HEADER}" AND
|
||||
EXISTS "${NEW_EDITOR_NATIVE_WINDOW_UI_COMPOSITOR_SOURCE}" AND
|
||||
EXISTS "${NEW_EDITOR_RHI_RENDER_BACKEND_SOURCE}" AND
|
||||
EXISTS "${NEW_EDITOR_RHI_COMMAND_COMPILER_SOURCE}" AND
|
||||
EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/test_native_window_ui_compositor.cpp")
|
||||
add_executable(new_editor_native_window_ui_compositor_tests
|
||||
test_native_window_ui_compositor.cpp
|
||||
${NEW_EDITOR_NATIVE_WINDOW_UI_COMPOSITOR_SOURCE}
|
||||
${NEW_EDITOR_RHI_RENDER_BACKEND_SOURCE}
|
||||
${NEW_EDITOR_RHI_COMMAND_COMPILER_SOURCE}
|
||||
)
|
||||
|
||||
xcengine_configure_new_editor_test_target(new_editor_native_window_ui_compositor_tests)
|
||||
|
||||
target_link_libraries(new_editor_native_window_ui_compositor_tests
|
||||
PRIVATE
|
||||
XCEngine
|
||||
GTest::gtest
|
||||
GTest::gtest_main
|
||||
user32
|
||||
comdlg32
|
||||
)
|
||||
|
||||
target_include_directories(new_editor_native_window_ui_compositor_tests PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/engine/include
|
||||
${CMAKE_SOURCE_DIR}/new_editor/src
|
||||
${CMAKE_SOURCE_DIR}/editor/src
|
||||
${CMAKE_BINARY_DIR}/_deps/imgui-src
|
||||
${CMAKE_BINARY_DIR}/_deps/imgui-src/backends
|
||||
)
|
||||
|
||||
xcengine_discover_new_editor_gtests(new_editor_native_window_ui_compositor_tests)
|
||||
else()
|
||||
message(STATUS "Skipping new_editor_native_window_ui_compositor_tests because native compositor/backend files or the test source are missing.")
|
||||
endif()
|
||||
|
||||
if(EXISTS "${NEW_EDITOR_NATIVE_BACKDROP_RENDERER_HEADER}")
|
||||
add_executable(new_editor_native_backdrop_renderer_api_tests
|
||||
test_main_window_native_backdrop_renderer_api.cpp
|
||||
@@ -824,3 +867,28 @@ if(EXISTS "${CMAKE_SOURCE_DIR}/new_editor/src/XCUIBackend/ImGuiXCUIPanelCanvasHo
|
||||
else()
|
||||
message(STATUS "Skipping new_editor_imgui_xcui_panel_canvas_host_tests because the ImGui host header, test source, or ImGui sources are missing.")
|
||||
endif()
|
||||
|
||||
if(EXISTS "${CMAKE_SOURCE_DIR}/new_editor/src/XCUIBackend/NativeXCUIPanelCanvasHost.h" AND
|
||||
EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/test_native_xcui_panel_canvas_host.cpp")
|
||||
add_executable(new_editor_native_xcui_panel_canvas_host_tests
|
||||
test_native_xcui_panel_canvas_host.cpp
|
||||
)
|
||||
|
||||
xcengine_configure_new_editor_test_target(new_editor_native_xcui_panel_canvas_host_tests)
|
||||
|
||||
target_link_libraries(new_editor_native_xcui_panel_canvas_host_tests
|
||||
PRIVATE
|
||||
XCEngine
|
||||
GTest::gtest
|
||||
GTest::gtest_main
|
||||
)
|
||||
|
||||
target_include_directories(new_editor_native_xcui_panel_canvas_host_tests PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/engine/include
|
||||
${CMAKE_SOURCE_DIR}/new_editor/src
|
||||
)
|
||||
|
||||
xcengine_discover_new_editor_gtests(new_editor_native_xcui_panel_canvas_host_tests)
|
||||
else()
|
||||
message(STATUS "Skipping new_editor_native_xcui_panel_canvas_host_tests because the native host header or test source is missing.")
|
||||
endif()
|
||||
|
||||
212
tests/NewEditor/test_native_window_ui_compositor.cpp
Normal file
212
tests/NewEditor/test_native_window_ui_compositor.cpp
Normal file
@@ -0,0 +1,212 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "XCUIBackend/IWindowUICompositor.h"
|
||||
#include "XCUIBackend/NativeWindowUICompositor.h"
|
||||
#include "XCUIBackend/UITextureRegistration.h"
|
||||
|
||||
#include <XCEngine/UI/DrawData.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
namespace {
|
||||
|
||||
using XCEngine::Editor::Platform::D3D12WindowRenderer;
|
||||
using XCEngine::Editor::XCUIBackend::CreateNativeWindowUICompositor;
|
||||
using XCEngine::Editor::XCUIBackend::IXCUITextAtlasProvider;
|
||||
using XCEngine::Editor::XCUIBackend::IWindowUICompositor;
|
||||
using XCEngine::Editor::XCUIBackend::NativeWindowUICompositor;
|
||||
using XCEngine::Editor::XCUIBackend::UITextureRegistration;
|
||||
using XCEngine::Editor::XCUIBackend::XCUINativeWindowPresentStats;
|
||||
using XCEngine::Editor::XCUIBackend::XCUINativeWindowRenderPacket;
|
||||
|
||||
HWND MakeFakeHwnd() {
|
||||
return reinterpret_cast<HWND>(static_cast<std::uintptr_t>(0x2345u));
|
||||
}
|
||||
|
||||
class StubTextAtlasProvider final : public IXCUITextAtlasProvider {
|
||||
public:
|
||||
bool GetAtlasTextureView(PixelFormat preferredFormat, AtlasTextureView& outView) const override {
|
||||
(void)preferredFormat;
|
||||
outView = {};
|
||||
return false;
|
||||
}
|
||||
|
||||
std::size_t GetFontCount() const override {
|
||||
return 0u;
|
||||
}
|
||||
|
||||
FontHandle GetFont(std::size_t index) const override {
|
||||
(void)index;
|
||||
return {};
|
||||
}
|
||||
|
||||
FontHandle GetDefaultFont() const override {
|
||||
return {};
|
||||
}
|
||||
|
||||
bool GetFontInfo(FontHandle font, FontInfo& outInfo) const override {
|
||||
(void)font;
|
||||
outInfo = {};
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GetBakedFontInfo(FontHandle font, float fontSize, BakedFontInfo& outInfo) const override {
|
||||
(void)font;
|
||||
(void)fontSize;
|
||||
outInfo = {};
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FindGlyph(FontHandle font, float fontSize, std::uint32_t codepoint, GlyphInfo& outInfo) const override {
|
||||
(void)font;
|
||||
(void)fontSize;
|
||||
(void)codepoint;
|
||||
outInfo = {};
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
XCEngine::UI::UIDrawData MakeDrawData() {
|
||||
XCEngine::UI::UIDrawData drawData = {};
|
||||
drawData.EmplaceDrawList("NativeOverlay").AddFilledRect(
|
||||
XCEngine::UI::UIRect(10.0f, 12.0f, 48.0f, 24.0f),
|
||||
XCEngine::UI::UIColor(0.2f, 0.4f, 0.8f, 1.0f));
|
||||
return drawData;
|
||||
}
|
||||
|
||||
TEST(NativeWindowUICompositorTest, RenderPacketReportsDrawDataPresenceAndClearResetsPayload) {
|
||||
XCUINativeWindowRenderPacket packet = {};
|
||||
EXPECT_FALSE(packet.HasDrawData());
|
||||
EXPECT_EQ(packet.textAtlasProvider, nullptr);
|
||||
|
||||
StubTextAtlasProvider atlasProvider = {};
|
||||
packet.drawData = MakeDrawData();
|
||||
packet.textAtlasProvider = &atlasProvider;
|
||||
|
||||
EXPECT_TRUE(packet.HasDrawData());
|
||||
EXPECT_EQ(packet.drawData.GetDrawListCount(), 1u);
|
||||
EXPECT_EQ(packet.drawData.GetTotalCommandCount(), 1u);
|
||||
EXPECT_EQ(packet.textAtlasProvider, &atlasProvider);
|
||||
|
||||
packet.Clear();
|
||||
EXPECT_FALSE(packet.HasDrawData());
|
||||
EXPECT_EQ(packet.drawData.GetDrawListCount(), 0u);
|
||||
EXPECT_EQ(packet.drawData.GetTotalCommandCount(), 0u);
|
||||
EXPECT_EQ(packet.textAtlasProvider, nullptr);
|
||||
}
|
||||
|
||||
TEST(NativeWindowUICompositorTest, SubmitAndClearPendingPacketTracksCopiedDrawDataAndAtlasProvider) {
|
||||
NativeWindowUICompositor compositor = {};
|
||||
StubTextAtlasProvider atlasProvider = {};
|
||||
const XCEngine::UI::UIDrawData drawData = MakeDrawData();
|
||||
|
||||
compositor.SubmitRenderPacket(drawData, &atlasProvider);
|
||||
ASSERT_TRUE(compositor.HasPendingRenderPacket());
|
||||
|
||||
const XCUINativeWindowRenderPacket& packet = compositor.GetPendingRenderPacket();
|
||||
EXPECT_TRUE(packet.HasDrawData());
|
||||
EXPECT_EQ(packet.drawData.GetDrawListCount(), 1u);
|
||||
EXPECT_EQ(packet.drawData.GetTotalCommandCount(), 1u);
|
||||
EXPECT_EQ(packet.textAtlasProvider, &atlasProvider);
|
||||
|
||||
compositor.ClearPendingRenderPacket();
|
||||
EXPECT_FALSE(compositor.HasPendingRenderPacket());
|
||||
EXPECT_FALSE(compositor.GetPendingRenderPacket().HasDrawData());
|
||||
EXPECT_EQ(compositor.GetPendingRenderPacket().textAtlasProvider, nullptr);
|
||||
}
|
||||
|
||||
TEST(NativeWindowUICompositorTest, InitializeAndShutdownResetStateAlongSafePaths) {
|
||||
NativeWindowUICompositor compositor = {};
|
||||
D3D12WindowRenderer renderer = {};
|
||||
compositor.SubmitRenderPacket(MakeDrawData(), nullptr);
|
||||
ASSERT_TRUE(compositor.HasPendingRenderPacket());
|
||||
|
||||
bool configureFontsCalled = false;
|
||||
EXPECT_FALSE(compositor.Initialize(
|
||||
nullptr,
|
||||
renderer,
|
||||
[&configureFontsCalled]() { configureFontsCalled = true; }));
|
||||
EXPECT_FALSE(configureFontsCalled);
|
||||
EXPECT_FALSE(compositor.HasPendingRenderPacket());
|
||||
|
||||
compositor.SubmitRenderPacket(MakeDrawData(), nullptr);
|
||||
EXPECT_TRUE(compositor.Initialize(
|
||||
MakeFakeHwnd(),
|
||||
renderer,
|
||||
[&configureFontsCalled]() { configureFontsCalled = true; }));
|
||||
EXPECT_FALSE(configureFontsCalled);
|
||||
EXPECT_FALSE(compositor.HasPendingRenderPacket());
|
||||
|
||||
compositor.Shutdown();
|
||||
EXPECT_FALSE(compositor.HasPendingRenderPacket());
|
||||
const XCUINativeWindowPresentStats& stats = compositor.GetLastPresentStats();
|
||||
EXPECT_FALSE(stats.hadPendingPacket);
|
||||
EXPECT_FALSE(stats.renderedNativeOverlay);
|
||||
EXPECT_EQ(stats.submittedDrawListCount, 0u);
|
||||
EXPECT_EQ(stats.submittedCommandCount, 0u);
|
||||
}
|
||||
|
||||
TEST(NativeWindowUICompositorTest, RenderFrameWithUnpreparedRendererSkipsCallbacksAndKeepsPendingPacket) {
|
||||
NativeWindowUICompositor compositor = {};
|
||||
D3D12WindowRenderer renderer = {};
|
||||
ASSERT_TRUE(compositor.Initialize(MakeFakeHwnd(), renderer, {}));
|
||||
|
||||
compositor.SubmitRenderPacket(MakeDrawData(), nullptr);
|
||||
ASSERT_TRUE(compositor.HasPendingRenderPacket());
|
||||
|
||||
bool uiRendered = false;
|
||||
bool beforeUiRendered = false;
|
||||
bool afterUiRendered = false;
|
||||
compositor.RenderFrame(
|
||||
std::array<float, 4>{ 0.1f, 0.2f, 0.3f, 1.0f }.data(),
|
||||
[&uiRendered]() { uiRendered = true; },
|
||||
[&beforeUiRendered](const ::XCEngine::Rendering::RenderContext&, const ::XCEngine::Rendering::RenderSurface&) {
|
||||
beforeUiRendered = true;
|
||||
},
|
||||
[&afterUiRendered](const ::XCEngine::Rendering::RenderContext&, const ::XCEngine::Rendering::RenderSurface&) {
|
||||
afterUiRendered = true;
|
||||
});
|
||||
|
||||
EXPECT_FALSE(uiRendered);
|
||||
EXPECT_FALSE(beforeUiRendered);
|
||||
EXPECT_FALSE(afterUiRendered);
|
||||
EXPECT_TRUE(compositor.HasPendingRenderPacket());
|
||||
|
||||
const XCUINativeWindowPresentStats& stats = compositor.GetLastPresentStats();
|
||||
EXPECT_FALSE(stats.hadPendingPacket);
|
||||
EXPECT_FALSE(stats.renderedNativeOverlay);
|
||||
EXPECT_EQ(stats.submittedDrawListCount, 0u);
|
||||
EXPECT_EQ(stats.submittedCommandCount, 0u);
|
||||
}
|
||||
|
||||
TEST(NativeWindowUICompositorTest, InterfaceFactoryReturnsSafeNativeCompositorDefaults) {
|
||||
std::unique_ptr<IWindowUICompositor> compositor = CreateNativeWindowUICompositor();
|
||||
ASSERT_NE(compositor, nullptr);
|
||||
|
||||
D3D12WindowRenderer renderer = {};
|
||||
bool configureFontsCalled = false;
|
||||
EXPECT_FALSE(compositor->Initialize(
|
||||
nullptr,
|
||||
renderer,
|
||||
[&configureFontsCalled]() { configureFontsCalled = true; }));
|
||||
EXPECT_FALSE(configureFontsCalled);
|
||||
EXPECT_FALSE(compositor->HandleWindowMessage(MakeFakeHwnd(), WM_CLOSE, 0u, 0u));
|
||||
|
||||
UITextureRegistration registration = {};
|
||||
EXPECT_FALSE(compositor->CreateTextureDescriptor(nullptr, nullptr, registration));
|
||||
EXPECT_EQ(registration.texture.nativeHandle, 0u);
|
||||
|
||||
bool uiRendered = false;
|
||||
compositor->RenderFrame(
|
||||
std::array<float, 4>{ 0.0f, 0.0f, 0.0f, 1.0f }.data(),
|
||||
[&uiRendered]() { uiRendered = true; },
|
||||
{},
|
||||
{});
|
||||
EXPECT_FALSE(uiRendered);
|
||||
|
||||
compositor->Shutdown();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
176
tests/NewEditor/test_native_xcui_panel_canvas_host.cpp
Normal file
176
tests/NewEditor/test_native_xcui_panel_canvas_host.cpp
Normal file
@@ -0,0 +1,176 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "XCUIBackend/NativeXCUIPanelCanvasHost.h"
|
||||
|
||||
namespace {
|
||||
|
||||
using XCEngine::Editor::XCUIBackend::CreateNativeXCUIPanelCanvasHost;
|
||||
using XCEngine::Editor::XCUIBackend::IXCUIPanelCanvasHost;
|
||||
using XCEngine::Editor::XCUIBackend::NativeXCUIPanelCanvasHost;
|
||||
using XCEngine::Editor::XCUIBackend::XCUIPanelCanvasFrameSnapshot;
|
||||
using XCEngine::Editor::XCUIBackend::XCUIPanelCanvasHostBackend;
|
||||
using XCEngine::Editor::XCUIBackend::XCUIPanelCanvasHostCapabilities;
|
||||
using XCEngine::Editor::XCUIBackend::XCUIPanelCanvasRequest;
|
||||
using XCEngine::Editor::XCUIBackend::XCUIPanelCanvasSession;
|
||||
|
||||
XCEngine::UI::UITextureHandle MakeSurfaceTextureHandle(std::uintptr_t nativeHandle, std::uint32_t width, std::uint32_t height) {
|
||||
XCEngine::UI::UITextureHandle texture = {};
|
||||
texture.nativeHandle = nativeHandle;
|
||||
texture.width = width;
|
||||
texture.height = height;
|
||||
texture.kind = XCEngine::UI::UITextureHandleKind::ShaderResourceView;
|
||||
return texture;
|
||||
}
|
||||
|
||||
TEST(NativeXCUIPanelCanvasHostTest, FactoryReportsNativeBackendCapabilitiesAndNoSnapshotBeforeBegin) {
|
||||
std::unique_ptr<IXCUIPanelCanvasHost> host = CreateNativeXCUIPanelCanvasHost();
|
||||
ASSERT_NE(host, nullptr);
|
||||
|
||||
EXPECT_STREQ(host->GetDebugName(), "NativeXCUIPanelCanvasHost");
|
||||
EXPECT_EQ(host->GetBackend(), XCUIPanelCanvasHostBackend::Native);
|
||||
|
||||
const XCUIPanelCanvasHostCapabilities capabilities = host->GetCapabilities();
|
||||
EXPECT_FALSE(capabilities.supportsPointerHitTesting);
|
||||
EXPECT_TRUE(capabilities.supportsHostedSurfaceImages);
|
||||
EXPECT_TRUE(capabilities.supportsPrimitiveOverlays);
|
||||
EXPECT_TRUE(capabilities.supportsExternallyDrivenSession);
|
||||
|
||||
XCUIPanelCanvasFrameSnapshot snapshot = {};
|
||||
EXPECT_FALSE(host->TryGetLatestFrameSnapshot(snapshot));
|
||||
EXPECT_TRUE(snapshot.childId.empty());
|
||||
EXPECT_EQ(snapshot.overlayDrawData.GetDrawListCount(), 0u);
|
||||
}
|
||||
|
||||
TEST(NativeXCUIPanelCanvasHostTest, BeginCanvasWithConfiguredSessionCapturesSnapshotAndOverlayCommands) {
|
||||
NativeXCUIPanelCanvasHost host = {};
|
||||
XCUIPanelCanvasSession configuredSession = {};
|
||||
configuredSession.hostRect = XCEngine::UI::UIRect(20.0f, 30.0f, 640.0f, 360.0f);
|
||||
configuredSession.canvasRect = XCEngine::UI::UIRect(20.0f, 72.0f, 640.0f, 318.0f);
|
||||
configuredSession.pointerPosition = XCEngine::UI::UIPoint(128.0f, 144.0f);
|
||||
configuredSession.validCanvas = true;
|
||||
configuredSession.hovered = true;
|
||||
configuredSession.windowFocused = true;
|
||||
host.SetCanvasSession(configuredSession);
|
||||
|
||||
XCUIPanelCanvasRequest request = {};
|
||||
request.childId = "NativeDemoCanvas";
|
||||
request.height = 360.0f;
|
||||
request.topInset = 42.0f;
|
||||
request.placeholderTitle = "Placeholder";
|
||||
request.placeholderSubtitle = "Native host placeholder";
|
||||
request.badgeTitle = "XCUI Demo";
|
||||
request.badgeSubtitle = "native path";
|
||||
|
||||
const XCUIPanelCanvasSession session = host.BeginCanvas(request);
|
||||
EXPECT_FLOAT_EQ(session.hostRect.x, configuredSession.hostRect.x);
|
||||
EXPECT_FLOAT_EQ(session.hostRect.y, configuredSession.hostRect.y);
|
||||
EXPECT_FLOAT_EQ(session.canvasRect.x, configuredSession.canvasRect.x);
|
||||
EXPECT_FLOAT_EQ(session.canvasRect.y, configuredSession.canvasRect.y);
|
||||
EXPECT_TRUE(session.validCanvas);
|
||||
EXPECT_TRUE(session.hovered);
|
||||
EXPECT_TRUE(session.windowFocused);
|
||||
|
||||
host.DrawFilledRect(
|
||||
XCEngine::UI::UIRect(40.0f, 90.0f, 120.0f, 60.0f),
|
||||
XCEngine::UI::UIColor(0.8f, 0.2f, 0.1f, 1.0f),
|
||||
6.0f);
|
||||
host.DrawOutlineRect(
|
||||
XCEngine::UI::UIRect(42.0f, 92.0f, 118.0f, 58.0f),
|
||||
XCEngine::UI::UIColor(0.2f, 0.8f, 0.3f, 1.0f),
|
||||
2.0f,
|
||||
4.0f);
|
||||
host.DrawText(
|
||||
XCEngine::UI::UIPoint(48.0f, 104.0f),
|
||||
"hello native",
|
||||
XCEngine::UI::UIColor(1.0f, 1.0f, 1.0f, 1.0f),
|
||||
16.0f);
|
||||
host.EndCanvas();
|
||||
|
||||
XCUIPanelCanvasFrameSnapshot snapshot = {};
|
||||
ASSERT_TRUE(host.TryGetLatestFrameSnapshot(snapshot));
|
||||
EXPECT_EQ(snapshot.childId, "NativeDemoCanvas");
|
||||
EXPECT_FALSE(snapshot.showingSurfaceImage);
|
||||
EXPECT_TRUE(snapshot.drawPreviewFrame);
|
||||
EXPECT_EQ(snapshot.placeholderTitle, "Placeholder");
|
||||
EXPECT_EQ(snapshot.placeholderSubtitle, "Native host placeholder");
|
||||
EXPECT_EQ(snapshot.badgeTitle, "XCUI Demo");
|
||||
EXPECT_EQ(snapshot.badgeSubtitle, "native path");
|
||||
EXPECT_TRUE(snapshot.session.validCanvas);
|
||||
EXPECT_EQ(snapshot.overlayDrawData.GetDrawListCount(), 1u);
|
||||
EXPECT_EQ(snapshot.overlayDrawData.GetDrawLists().front().GetDebugName(), "NativeDemoCanvas.overlay");
|
||||
EXPECT_EQ(snapshot.overlayDrawData.GetTotalCommandCount(), 14u);
|
||||
EXPECT_EQ(
|
||||
snapshot.overlayDrawData.GetDrawLists().front().GetCommands().front().type,
|
||||
XCEngine::UI::UIDrawCommandType::PushClipRect);
|
||||
EXPECT_EQ(
|
||||
snapshot.overlayDrawData.GetDrawLists().front().GetCommands().back().type,
|
||||
XCEngine::UI::UIDrawCommandType::PopClipRect);
|
||||
}
|
||||
|
||||
TEST(NativeXCUIPanelCanvasHostTest, SurfaceImagePathCapturesSurfaceAndPreviewFrameWithoutPlaceholder) {
|
||||
NativeXCUIPanelCanvasHost host = {};
|
||||
XCUIPanelCanvasSession configuredSession = {};
|
||||
configuredSession.hostRect = XCEngine::UI::UIRect(0.0f, 0.0f, 800.0f, 480.0f);
|
||||
configuredSession.canvasRect = XCEngine::UI::UIRect(0.0f, 0.0f, 800.0f, 480.0f);
|
||||
configuredSession.validCanvas = true;
|
||||
host.SetCanvasSession(configuredSession);
|
||||
|
||||
XCUIPanelCanvasRequest request = {};
|
||||
request.childId = "NativeSurfaceCanvas";
|
||||
request.showSurfaceImage = true;
|
||||
request.drawPreviewFrame = true;
|
||||
request.surfaceImage.texture = MakeSurfaceTextureHandle(17u, 1024u, 512u);
|
||||
request.surfaceImage.surfaceWidth = 1024u;
|
||||
request.surfaceImage.surfaceHeight = 512u;
|
||||
request.surfaceImage.uvMin = XCEngine::UI::UIPoint(0.0f, 0.0f);
|
||||
request.surfaceImage.uvMax = XCEngine::UI::UIPoint(1.0f, 1.0f);
|
||||
|
||||
const XCUIPanelCanvasSession session = host.BeginCanvas(request);
|
||||
EXPECT_TRUE(session.validCanvas);
|
||||
host.EndCanvas();
|
||||
|
||||
XCUIPanelCanvasFrameSnapshot snapshot = {};
|
||||
ASSERT_TRUE(host.TryGetLatestFrameSnapshot(snapshot));
|
||||
EXPECT_TRUE(snapshot.showingSurfaceImage);
|
||||
EXPECT_TRUE(snapshot.surfaceImage.IsValid());
|
||||
EXPECT_EQ(snapshot.surfaceImage.texture.nativeHandle, 17u);
|
||||
EXPECT_EQ(snapshot.overlayDrawData.GetDrawListCount(), 1u);
|
||||
EXPECT_EQ(snapshot.overlayDrawData.GetTotalCommandCount(), 3u);
|
||||
|
||||
const auto& commands = snapshot.overlayDrawData.GetDrawLists().front().GetCommands();
|
||||
ASSERT_EQ(commands.size(), 3u);
|
||||
EXPECT_EQ(commands[0].type, XCEngine::UI::UIDrawCommandType::PushClipRect);
|
||||
EXPECT_EQ(commands[1].type, XCEngine::UI::UIDrawCommandType::RectOutline);
|
||||
EXPECT_EQ(commands[2].type, XCEngine::UI::UIDrawCommandType::PopClipRect);
|
||||
}
|
||||
|
||||
TEST(NativeXCUIPanelCanvasHostTest, ClearingConfiguredSessionFallsBackToPassiveSnapshot) {
|
||||
NativeXCUIPanelCanvasHost host = {};
|
||||
XCUIPanelCanvasSession configuredSession = {};
|
||||
configuredSession.hostRect = XCEngine::UI::UIRect(4.0f, 5.0f, 320.0f, 240.0f);
|
||||
configuredSession.canvasRect = XCEngine::UI::UIRect(4.0f, 25.0f, 320.0f, 220.0f);
|
||||
configuredSession.validCanvas = true;
|
||||
host.SetCanvasSession(configuredSession);
|
||||
ASSERT_TRUE(host.HasConfiguredSession());
|
||||
|
||||
host.ClearCanvasSession();
|
||||
EXPECT_FALSE(host.HasConfiguredSession());
|
||||
|
||||
XCUIPanelCanvasRequest request = {};
|
||||
request.height = 180.0f;
|
||||
request.topInset = 24.0f;
|
||||
const XCUIPanelCanvasSession session = host.BeginCanvas(request);
|
||||
host.EndCanvas();
|
||||
|
||||
EXPECT_FALSE(session.validCanvas);
|
||||
EXPECT_FLOAT_EQ(session.hostRect.height, 180.0f);
|
||||
EXPECT_FLOAT_EQ(session.canvasRect.y, 24.0f);
|
||||
EXPECT_FLOAT_EQ(session.canvasRect.height, 156.0f);
|
||||
|
||||
XCUIPanelCanvasFrameSnapshot snapshot = {};
|
||||
ASSERT_TRUE(host.TryGetLatestFrameSnapshot(snapshot));
|
||||
EXPECT_FALSE(snapshot.session.validCanvas);
|
||||
EXPECT_EQ(snapshot.overlayDrawData.GetDrawListCount(), 0u);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
Reference in New Issue
Block a user