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
|
- `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
|
- 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`
|
- `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`.
|
- Old `editor` replacement remains deferred; all active execution still stays inside XCUI shared code and `new_editor`.
|
||||||
|
|
||||||
## Three-Layer Status
|
## 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.
|
- `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.
|
- `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.
|
- `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`.
|
- `XCNewEditor` builds successfully to `build/new_editor/bin/Debug/XCNewEditor.exe`.
|
||||||
|
|
||||||
Current gap:
|
Current gap:
|
||||||
|
|
||||||
- The shell is still ImGui-hosted.
|
- 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 compatibility presentation still depends on an ImGui-specific inline draw target binding when the native queued surface path is disabled.
|
- 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 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.
|
- 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, 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.
|
- 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
|
## Validated This Phase
|
||||||
|
|
||||||
@@ -113,11 +129,13 @@ Current gap:
|
|||||||
- `new_editor_xcui_rhi_render_backend_tests`: `5/5`
|
- `new_editor_xcui_rhi_render_backend_tests`: `5/5`
|
||||||
- `new_editor_xcui_hosted_preview_presenter_tests`: `20/20`
|
- `new_editor_xcui_hosted_preview_presenter_tests`: `20/20`
|
||||||
- `new_editor_imgui_window_ui_compositor_tests`: `7/7`
|
- `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_xcui_editor_command_router_tests`: `5/5`
|
||||||
- `new_editor_application_shell_command_bindings_tests`: `6/6`
|
- `new_editor_application_shell_command_bindings_tests`: `6/6`
|
||||||
- `new_editor_xcui_shell_chrome_state_tests`: `11/11`
|
- `new_editor_xcui_shell_chrome_state_tests`: `11/11`
|
||||||
- `new_editor_xcui_panel_canvas_host_tests`: `4/4`
|
- `new_editor_xcui_panel_canvas_host_tests`: `4/4`
|
||||||
- `new_editor_imgui_xcui_panel_canvas_host_tests`: `1/1`
|
- `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`
|
- `new_editor_xcui_layout_lab_panel_tests`: `3/3`
|
||||||
- `XCNewEditor` Debug target builds successfully
|
- `XCNewEditor` Debug target builds successfully
|
||||||
- `core_ui_tests`: `52 total` (`50` passed, `2` skipped because `KeyCode::Delete` currently aliases `Backspace`)
|
- `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` 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.
|
- `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 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:
|
- `SceneRuntime` layered XCUI routing now has dedicated regression coverage for:
|
||||||
- top-interactive layer input ownership
|
- top-interactive layer input ownership
|
||||||
- blocking/modal layer suppression of lower layers
|
- blocking/modal layer suppression of lower layers
|
||||||
|
|||||||
@@ -2,13 +2,16 @@
|
|||||||
#include "XCUIBackend/ImGuiXCUIPanelCanvasHost.h"
|
#include "XCUIBackend/ImGuiXCUIPanelCanvasHost.h"
|
||||||
#include "XCUIBackend/ImGuiXCUIHostedPreviewPresenter.h"
|
#include "XCUIBackend/ImGuiXCUIHostedPreviewPresenter.h"
|
||||||
#include "XCUIBackend/ImGuiWindowUICompositor.h"
|
#include "XCUIBackend/ImGuiWindowUICompositor.h"
|
||||||
|
#include "XCUIBackend/NativeWindowUICompositor.h"
|
||||||
|
|
||||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||||
|
#include <XCEngine/UI/DrawData.h>
|
||||||
|
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
namespace XCEngine {
|
namespace XCEngine {
|
||||||
namespace NewEditor {
|
namespace NewEditor {
|
||||||
@@ -18,6 +21,14 @@ constexpr wchar_t kWindowClassName[] = L"XCNewEditorWindowClass";
|
|||||||
constexpr wchar_t kWindowTitle[] = L"XCNewEditor";
|
constexpr wchar_t kWindowTitle[] = L"XCNewEditor";
|
||||||
constexpr float kClearColor[4] = { 0.08f, 0.09f, 0.11f, 1.0f };
|
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 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>
|
template <typename ResourceType>
|
||||||
void ShutdownAndDelete(ResourceType*& resource) {
|
void ShutdownAndDelete(ResourceType*& resource) {
|
||||||
@@ -78,6 +89,100 @@ const char* GetHostedPreviewStateLabel(
|
|||||||
}
|
}
|
||||||
return "idle";
|
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
|
} // namespace
|
||||||
|
|
||||||
std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter>
|
std::unique_ptr<::XCEngine::Editor::XCUIBackend::IXCUIHostedPreviewPresenter>
|
||||||
@@ -91,6 +196,22 @@ Application::CreateHostedPreviewPresenter(bool nativePreview) {
|
|||||||
return ::XCEngine::Editor::XCUIBackend::CreateImGuiXCUIHostedPreviewPresenter();
|
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 {
|
const Application::ShellPanelChromeState* Application::TryGetShellPanelState(ShellPanelId panelId) const {
|
||||||
return m_shellChromeState.TryGetPanelState(panelId);
|
return m_shellChromeState.TryGetPanelState(panelId);
|
||||||
}
|
}
|
||||||
@@ -197,20 +318,25 @@ void Application::ConfigureShellCommandRouter() {
|
|||||||
Application::RegisterShellViewCommands(m_shellCommandRouter, bindings);
|
Application::RegisterShellViewCommands(m_shellCommandRouter, bindings);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::DispatchShellShortcuts() {
|
::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameDelta
|
||||||
::XCEngine::Editor::XCUIBackend::XCUIInputBridgeCaptureOptions options = {};
|
Application::DispatchShellShortcuts(
|
||||||
::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot snapshot =
|
const ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot& snapshot) {
|
||||||
m_xcuiInputSource.CaptureSnapshot(options);
|
::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameSnapshot shellSnapshot = snapshot;
|
||||||
|
if (!IsNativeWindowHostEnabled()) {
|
||||||
ImGuiIO& io = ImGui::GetIO();
|
ImGuiIO& io = ImGui::GetIO();
|
||||||
snapshot.wantCaptureKeyboard = io.WantCaptureKeyboard;
|
shellSnapshot.wantCaptureKeyboard = io.WantCaptureKeyboard;
|
||||||
snapshot.wantTextInput = io.WantTextInput;
|
shellSnapshot.wantTextInput = io.WantTextInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_shellInputBridge.HasBaseline()) {
|
||||||
|
m_shellInputBridge.Prime(shellSnapshot);
|
||||||
|
}
|
||||||
const ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameDelta frameDelta =
|
const ::XCEngine::Editor::XCUIBackend::XCUIInputBridgeFrameDelta frameDelta =
|
||||||
m_shellInputBridge.Translate(snapshot);
|
m_shellInputBridge.Translate(shellSnapshot);
|
||||||
const ::XCEngine::Editor::XCUIBackend::XCUIEditorCommandInputSnapshot commandSnapshot =
|
const ::XCEngine::Editor::XCUIBackend::XCUIEditorCommandInputSnapshot commandSnapshot =
|
||||||
Application::BuildShellShortcutSnapshot(frameDelta);
|
Application::BuildShellShortcutSnapshot(frameDelta);
|
||||||
m_shellCommandRouter.InvokeMatchingShortcut({ &commandSnapshot });
|
m_shellCommandRouter.InvokeMatchingShortcut({ &commandSnapshot });
|
||||||
|
return frameDelta;
|
||||||
}
|
}
|
||||||
|
|
||||||
Application::HostedPreviewPanelDiagnostics Application::BuildHostedPreviewPanelDiagnostics(
|
Application::HostedPreviewPanelDiagnostics Application::BuildHostedPreviewPanelDiagnostics(
|
||||||
@@ -293,15 +419,19 @@ int Application::Run(HINSTANCE instance, int nCmdShow) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
InitializeWindowCompositor();
|
InitializeWindowCompositor();
|
||||||
m_demoPanel = std::make_unique<XCUIDemoPanel>(
|
if (IsNativeWindowHostEnabled()) {
|
||||||
&m_xcuiInputSource,
|
InitializeNativeShell();
|
||||||
CreateHostedPreviewPresenter(IsNativeHostedPreviewEnabled(ShellPanelId::XCUIDemo)),
|
} else {
|
||||||
::XCEngine::Editor::XCUIBackend::CreateImGuiXCUIPanelCanvasHost());
|
m_demoPanel = std::make_unique<XCUIDemoPanel>(
|
||||||
m_layoutLabPanel = std::make_unique<XCUILayoutLabPanel>(
|
&m_xcuiInputSource,
|
||||||
&m_xcuiInputSource,
|
CreateHostedPreviewPresenter(IsNativeHostedPreviewEnabled(ShellPanelId::XCUIDemo)),
|
||||||
CreateHostedPreviewPresenter(IsNativeHostedPreviewEnabled(ShellPanelId::XCUILayoutLab)),
|
::XCEngine::Editor::XCUIBackend::CreateImGuiXCUIPanelCanvasHost());
|
||||||
::XCEngine::Editor::XCUIBackend::CreateImGuiXCUIPanelCanvasHost());
|
m_layoutLabPanel = std::make_unique<XCUILayoutLabPanel>(
|
||||||
ConfigureHostedPreviewPresenters();
|
&m_xcuiInputSource,
|
||||||
|
CreateHostedPreviewPresenter(IsNativeHostedPreviewEnabled(ShellPanelId::XCUILayoutLab)),
|
||||||
|
::XCEngine::Editor::XCUIBackend::CreateImGuiXCUIPanelCanvasHost());
|
||||||
|
ConfigureHostedPreviewPresenters();
|
||||||
|
}
|
||||||
m_shellInputBridge.Reset();
|
m_shellInputBridge.Reset();
|
||||||
ConfigureShellCommandRouter();
|
ConfigureShellCommandRouter();
|
||||||
m_running = true;
|
m_running = true;
|
||||||
@@ -433,7 +563,9 @@ bool Application::InitializeRenderer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Application::InitializeWindowCompositor() {
|
void Application::InitializeWindowCompositor() {
|
||||||
m_windowCompositor = ::XCEngine::Editor::XCUIBackend::CreateImGuiWindowUICompositor();
|
m_windowCompositor = IsNativeWindowHostEnabled()
|
||||||
|
? ::XCEngine::Editor::XCUIBackend::CreateNativeWindowUICompositor()
|
||||||
|
: ::XCEngine::Editor::XCUIBackend::CreateImGuiWindowUICompositor();
|
||||||
if (m_windowCompositor != nullptr) {
|
if (m_windowCompositor != nullptr) {
|
||||||
m_windowCompositor->Initialize(
|
m_windowCompositor->Initialize(
|
||||||
m_hwnd,
|
m_hwnd,
|
||||||
@@ -654,6 +786,525 @@ bool Application::RenderHostedPreviewOffscreenSurface(
|
|||||||
return true;
|
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() {
|
void Application::RenderShellChrome() {
|
||||||
SyncShellChromePanelStateFromPanels();
|
SyncShellChromePanelStateFromPanels();
|
||||||
|
|
||||||
@@ -1023,75 +1674,17 @@ void Application::Frame() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_hostedPreviewQueue.BeginFrame();
|
|
||||||
m_hostedPreviewSurfaceRegistry.BeginFrame();
|
|
||||||
SyncHostedPreviewSurfaces();
|
|
||||||
if (m_windowCompositor == nullptr) {
|
if (m_windowCompositor == nullptr) {
|
||||||
m_xcuiInputSource.ClearFrameTransients();
|
m_xcuiInputSource.ClearFrameTransients();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_windowCompositor->RenderFrame(
|
if (IsNativeWindowHostEnabled()) {
|
||||||
kClearColor,
|
FrameNativeXCUIHost();
|
||||||
[this]() {
|
return;
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
SyncShellChromePanelStateFromPanels();
|
FrameLegacyImGuiHost();
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace NewEditor
|
} // namespace NewEditor
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
#include "Platform/D3D12WindowRenderer.h"
|
#include "Platform/D3D12WindowRenderer.h"
|
||||||
#include "Rendering/MainWindowNativeBackdropRenderer.h"
|
#include "Rendering/MainWindowNativeBackdropRenderer.h"
|
||||||
#include "XCUIBackend/IWindowUICompositor.h"
|
#include "XCUIBackend/IWindowUICompositor.h"
|
||||||
|
#include "XCUIBackend/NativeXCUIPanelCanvasHost.h"
|
||||||
|
#include "XCUIBackend/XCUIDemoRuntime.h"
|
||||||
#include "XCUIBackend/XCUIHostedPreviewPresenter.h"
|
#include "XCUIBackend/XCUIHostedPreviewPresenter.h"
|
||||||
#include "XCUIBackend/XCUIInputBridge.h"
|
#include "XCUIBackend/XCUIInputBridge.h"
|
||||||
#include "XCUIBackend/XCUILayoutLabRuntime.h"
|
#include "XCUIBackend/XCUILayoutLabRuntime.h"
|
||||||
@@ -36,6 +38,10 @@ public:
|
|||||||
using ShellHostedPreviewMode = ::XCEngine::Editor::XCUIBackend::XCUIShellHostedPreviewMode;
|
using ShellHostedPreviewMode = ::XCEngine::Editor::XCUIBackend::XCUIShellHostedPreviewMode;
|
||||||
using ShellPanelChromeState = ::XCEngine::Editor::XCUIBackend::XCUIShellPanelChromeState;
|
using ShellPanelChromeState = ::XCEngine::Editor::XCUIBackend::XCUIShellPanelChromeState;
|
||||||
using ShellViewToggleState = ::XCEngine::Editor::XCUIBackend::XCUIShellViewToggleState;
|
using ShellViewToggleState = ::XCEngine::Editor::XCUIBackend::XCUIShellViewToggleState;
|
||||||
|
enum class WindowHostMode : std::uint8_t {
|
||||||
|
NativeXCUI = 0,
|
||||||
|
LegacyImGui
|
||||||
|
};
|
||||||
|
|
||||||
struct ShellCommandIds {
|
struct ShellCommandIds {
|
||||||
static constexpr const char* ToggleXCUIDemoPanel =
|
static constexpr const char* ToggleXCUIDemoPanel =
|
||||||
@@ -297,6 +303,7 @@ private:
|
|||||||
bool CreateMainWindow(HINSTANCE instance, int nCmdShow);
|
bool CreateMainWindow(HINSTANCE instance, int nCmdShow);
|
||||||
bool InitializeRenderer();
|
bool InitializeRenderer();
|
||||||
void InitializeWindowCompositor();
|
void InitializeWindowCompositor();
|
||||||
|
void InitializeNativeShell();
|
||||||
void ShutdownWindowCompositor();
|
void ShutdownWindowCompositor();
|
||||||
void ShutdownRenderer();
|
void ShutdownRenderer();
|
||||||
void DestroyHostedPreviewSurfaces();
|
void DestroyHostedPreviewSurfaces();
|
||||||
@@ -329,7 +336,14 @@ private:
|
|||||||
const ::XCEngine::Rendering::RenderContext& renderContext,
|
const ::XCEngine::Rendering::RenderContext& renderContext,
|
||||||
const ::XCEngine::UI::UIDrawData& drawData);
|
const ::XCEngine::UI::UIDrawData& drawData);
|
||||||
void ConfigureShellCommandRouter();
|
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 RenderShellChrome();
|
||||||
void RenderHostedPreviewHud();
|
void RenderHostedPreviewHud();
|
||||||
void RenderQueuedHostedPreviews(
|
void RenderQueuedHostedPreviews(
|
||||||
@@ -351,6 +365,16 @@ private:
|
|||||||
::XCEngine::Editor::XCUIBackend::XCUIRHIRenderBackend m_hostedPreviewRenderBackend;
|
::XCEngine::Editor::XCUIBackend::XCUIRHIRenderBackend m_hostedPreviewRenderBackend;
|
||||||
ShellChromeState m_shellChromeState = {};
|
ShellChromeState m_shellChromeState = {};
|
||||||
std::vector<HostedPreviewOffscreenSurface> m_hostedPreviewSurfaces = {};
|
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;
|
::XCEngine::Editor::XCUIBackend::XCUILayoutLabRuntime m_nativeOverlayRuntime;
|
||||||
MainWindowNativeBackdropRenderer m_nativeBackdropRenderer;
|
MainWindowNativeBackdropRenderer m_nativeBackdropRenderer;
|
||||||
bool m_running = false;
|
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 <algorithm>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cmath>
|
|
||||||
#include <limits>
|
|
||||||
|
|
||||||
namespace XCEngine {
|
namespace XCEngine {
|
||||||
namespace Editor {
|
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() {
|
std::uint64_t GetTimestampNanoseconds() {
|
||||||
return static_cast<std::uint64_t>(
|
return static_cast<std::uint64_t>(
|
||||||
std::chrono::duration_cast<std::chrono::nanoseconds>(
|
std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||||
|
|||||||
@@ -3,8 +3,6 @@
|
|||||||
#include <XCEngine/Input/InputTypes.h>
|
#include <XCEngine/Input/InputTypes.h>
|
||||||
#include <XCEngine/UI/Types.h>
|
#include <XCEngine/UI/Types.h>
|
||||||
|
|
||||||
#include <imgui.h>
|
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|||||||
@@ -136,6 +136,12 @@ set(NEW_EDITOR_WINDOW_UI_COMPOSITOR_HEADER
|
|||||||
set(NEW_EDITOR_IMGUI_WINDOW_UI_COMPOSITOR_HEADER
|
set(NEW_EDITOR_IMGUI_WINDOW_UI_COMPOSITOR_HEADER
|
||||||
${CMAKE_SOURCE_DIR}/new_editor/src/XCUIBackend/ImGuiWindowUICompositor.h
|
${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
|
set(NEW_EDITOR_NATIVE_BACKDROP_RENDERER_HEADER
|
||||||
${CMAKE_SOURCE_DIR}/new_editor/src/Rendering/MainWindowNativeBackdropRenderer.h
|
${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.")
|
message(STATUS "Skipping new_editor_imgui_window_ui_compositor_tests because compositor headers or the test source are missing.")
|
||||||
endif()
|
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}")
|
if(EXISTS "${NEW_EDITOR_NATIVE_BACKDROP_RENDERER_HEADER}")
|
||||||
add_executable(new_editor_native_backdrop_renderer_api_tests
|
add_executable(new_editor_native_backdrop_renderer_api_tests
|
||||||
test_main_window_native_backdrop_renderer_api.cpp
|
test_main_window_native_backdrop_renderer_api.cpp
|
||||||
@@ -824,3 +867,28 @@ if(EXISTS "${CMAKE_SOURCE_DIR}/new_editor/src/XCUIBackend/ImGuiXCUIPanelCanvasHo
|
|||||||
else()
|
else()
|
||||||
message(STATUS "Skipping new_editor_imgui_xcui_panel_canvas_host_tests because the ImGui host header, test source, or ImGui sources are missing.")
|
message(STATUS "Skipping new_editor_imgui_xcui_panel_canvas_host_tests because the ImGui host header, test source, or ImGui sources are missing.")
|
||||||
endif()
|
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